Tags C & C++

C++11, the new C++ standard (formally known as ISO/IEC 14882:2011) has been finally approved in September 2011, after many years in the making. It's a major change with a lot of new features added to the language. C++ was never an easy language to grok, and without doubt C++11 makes it even more complex. However, it also introduces some features that are expected to make the lives of programmers easier, addressing several usability problems with the language.

One new feature that should make C++ a bit more pleasant to use is lambda functions. I'm assuming the reader is familiar with the concept - if not, please consult this Wikipedia page.

Here's a simple example of using an STL algorithm (count_if), without a lambda (the way it has to be done in C++ prior to C++11):

bool isEven(int elem)
{
    return elem % 2 == 0;
}

int main()
{
    // Using initializer lists for vectors - another cool C++11 feature!
    vector<int> v = {4, 5, 6, 8, 7, 999, 212};

    int numeven = count_if(v.begin(), v.end(), isEven);
    cout << "num of evens: " << numeven << endl;
    return 0;
}

count_if is one of a multitude of algorithms STL provides out of the box, but creating special functions like isEven is a chore, so programmers clamored for something simpler for a long time. Well, here's C++11:

int numeven = count_if(v.begin(), v.end(),
    [] (int num) {return num % 2 == 0;});
cout << "num of evens: " << numeven << endl;

The code of isEven is now written inline; we no longer need to define a separate function! Note the funny [] (...) {} syntax - this is the lambda. Well, at least you can't blame the standard folks for not fully utilizing their brace types ;-)

Seriously, though, IMO this is a big improvement. lambdas and STL algorithms are a perfect match; they save most of the grunt work required to use many algorithms. You don't even have to specify the return type explicitly in 99% of the cases, when it can be inferred by the compiler. For the other 1%, you can always do it with special syntax.

Lambdas can also use variables from the code that contains them, through "capturing":

// Using initializer lists for vectors
vector<int> v = {4, 5, 6, 8, 7, 999, 212};

// addend is captured by value
int addend = 13;
int numeven = count_if(v.begin(), v.end(),
    [addend] (int num) {return (addend + num) % 2 == 0;});

Here addend is captured and used by the lambda. Note that it is explicitly specified inside the brackets. We could also write [=], which means "capture all visible variables, by value". Another variable that a lambda can capture is the implicit this found in class methods. You have to specify it explicitly, and then the lambda can use it to access other class members.

Variables can also be captured by reference, which allows the lambda to modify them. Here's a sample:

// Using initializer lists for vectors
vector<int> v = {4, 5, 6, 8, 7, 999, 212};

int total = 0;
for_each(v.begin(), v.end(),
    [&total] (int num) {
        if (num % 2 == 0) {
            total += num;
        }
});
cout << "total: " << total << endl;

Here the lambda sums all even numbers into total, which is modified by it. Note the notation - & is used to denote "capture by reference". A fuller explanation of what can go into the brackets of a lambda can be found here.

Now would be a good time to explain that there's nothing magical about lambdas. They're simply syntactic sugar for the more wordy implementation you'd have to opt for, were you using the old C++ standard - with functors. In fact, the previous example is exactly equivalent to this:

int total = 0;
for_each(v.begin(), v.end(), AccumulatorFunctor(total));
cout << "total: " << total << endl;

Where AccumulatorFunctor is:

// Functor equivalent to the ref-capturing-lambda
class AccumulatorFunctor {
public:
    AccumulatorFunctor(int& total)
        : m_total(total) {}

    void operator()(int num) {
        if (num % 2 == 0) {
            m_total += num;
        }
    }
private:
    int& m_total;
};

This fact is important to keep in mind when thinking about the more advanced usages of lambdas. Unfortunately, it has become common to refer to C++ lambdas as closures. IMHO this is misleading, because C++ lambdas aren't closures in the full sense, simply because the execution and memory models of C++ just don't work that way. The most important limitation to remember about C++ lambdas is stated in the aforementioned Wikipedia article:

If a closure object containing references to local variables is invoked after the innermost block scope of its creation, the behaviour is undefined.

This means that C++ lambdas can act as closures only in a limited scope, while the variables they capture are alive on the stack. The equivalent functor example above should make this very clear - a reference to a local variable will be invalid after this variable goes out of scope.

In other words, this canonical example (written in Scheme) of what closures are capable of, is not possible with C++11 lambdas. make-withdraw creates entities that can be called with a number, and subtract this number from an initial amount that was specified when make-withdraw is called. This state is different for each instance of make-withdraw, but is saved between invocations of the entity it creates. In other words, this is how rudimentary objects are created with closures:

(define (make-withdraw balance)
    (lambda (amount)
        (if (>= balance amount)
            (begin (set! balance (- balance amount))
                    balance)
            'no-funds)))

(define W1 (make-withdraw 100))
(define W2 (make-withdraw 500))
(write (W1 20))
(write (W2 30))

This is not to detriment from the usefulness of lambdas, however. Such usages of closures are usually just implemented differently in C++ (with objects). Lambdas simply have another goal in this language. I just wanted to point out that referring to C++ lambdas as closures is misleading.

Before I finish, another interesting feature of lambdas I wanted to mention - lambdas are actually first-class objects in C++ - they can be saved in variables, passed as parameters and be returned from functions. Here's a small example:

vector<int> v1 = {4, 5, 6, 8, 7, 999, 212};
vector<int> v2 = {3, 2, 5};

char delim = ',';
auto printer = [=] (int elem) {
    cout << elem << delim;
};

for_each(v1.begin(), v1.end(), printer);
cout << "\n";
for_each(v2.begin(), v2.end(), printer);

printer is a function that can be passed around and returned, and it has been created with a lambda. Note the usage of the new C++11 keyword (or, more precisely - the new meaning of an old keyword) auto to save our poor fingers from explicitly writing types the compiler could infer on its own.

To conclude, lambda is a useful feature to know about. When used properly, it can make some code simpler to write. When used improperly, it can give a hand in obfuscated code contests. Also, it's important to keep the limitation of lambdas in mind. lambdas in C++ are just syntactic sugar, not true lexical closures.


Comments

comments powered by Disqus