Lambdas, auto, and static_assert: C++0x Features in VC10, Part 1

Visual CPP Team

The Visual C++ compiler in the Microsoft Visual Studio 2010 September Community Technology Preview (CTP) contains support for four C++0x language features, namely lambdas, auto, static_assert, and rvalue references.  Today, I’m going to explain the first three features in detail.  (And I’m going to dedicate an entire post to explaining rvalue references in the near future, simply because explaining them now would double the length of this already very long post.)

 

First, a few quick things:

 

1. Today’s post is brought to you by Stephan T. Lavavej, Visual C++ Libraries Developer, and the letters C, A, and T.  Note that as a libraries dev, I didn’t implement these features.  That was the work of Jonathan Caves, front-end compiler dev, voting Standardization Committee member, and all-around ninja.

 

2. I refer to the Visual C++ compiler in VS 2010 as VC10 (VS 2008 contained VC9, VS 2005 contained VC8, etc. – 10 is not short for 2010).

 

3. C++0x refers to the upcoming C++ Standard, which is still being drafted.  (The Standardization Committee hopes that they’ll be finished in 2009, making it C++09; the joke is that if it slips to 2010 or later, the ‘x’ will be hexadecimal.)  C++98 and C++03 refer to the current C++ Standard.  (Without going into a history lecture here, the 2003 C++ Standard was merely a “service pack” for the original 1998 C++ Standard, and most people can disregard the differences.  C++03 and C++0x are totally different, despite appearances.)

 

4. I’d like to thank the Standardization Committee for developing these wonderfully useful and well-crafted features.  They also make important documents available on their website:

 

C++0x language feature status: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html

C++0x library feature status: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html

C++0x Working Draft: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf

 

5. There are always bugs (although hopefully not too many), which is the whole point of the CTP.  Please report bugs to us via Microsoft Connect.

 

Now, let’s look at the features!

 

 

lambdas

In C++0x, “lambda expressions” implicitly define and construct unnamed function objects, which then behave like handwritten function objects.  This is the “Hello, World” lambda:

 

C:Temp>type meow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << ” “; });

    cout << endl;

}

 

C:Temp>cl /EHsc /nologo /W4 meow.cpp > NUL && meow

0 1 2 3 4 5 6 7 8 9

 

The [] is the lambda-introducer, which tells the compiler that a lambda expression is beginning.  The (int n) is the lambda-parameter-declaration, which tells the compiler what the unnamed function object class’s function call operator should take.  Finally, the { cout << n << ” “; } is the compound-statement which serves as the body of the unnamed function object class’s function call operator.  By default, the unnamed function object class’s function call operator returns void.

 

 

So, C++0x has mentally translated this into what you’d write in C++98:

 

C:Temp>type meow98.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

struct LambdaFunctor {

    void operator()(int n) const {

        cout << n << ” “;

    }

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), LambdaFunctor());

    cout << endl;

}

 

C:Temp>cl /EHsc /nologo /W4 meow98.cpp > NUL && meow98

0 1 2 3 4 5 6 7 8 9

 

Now I’m going to stop saying things like “the unnamed function object class’s function call operator returns void” and start saying “the lambda returns void“, but it’s important to remember what lambda expressions are doing: defining classes and constructing objects.

 

 

Of course, the compound-statement of a lambda can contain multiple statements:

 

C:Temp>type multimeow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), [](int n) {

        cout << n;

 

        if (n % 2 == 0) {

            cout << ” even “;

        } else {

            cout << ” odd “;

        }

    });

 

    cout << endl;

}

 

C:Temp>cl /EHsc /nologo /W4 multimeow.cpp > NUL && multimeow

0 even 1 odd 2 even 3 odd 4 even 5 odd 6 even 7 odd 8 even 9 odd

 

 

Now, lambdas don’t always have to return void.  If a lambda’s compound-statement is { return expression; } , then the lambda’s return type will be automatically deduced to be the type of expression:

 

C:Temp>type cubicmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    deque<int> d;

 

    transform(v.begin(), v.end(), front_inserter(d), [](int n) { return n * n * n; });

 

    for_each(d.begin(), d.end(), [](int n) { cout << n << ” “; });

    cout << endl;

}

 

C:Temp>cl /EHsc /nologo /W4 cubicmeow.cpp > NUL && cubicmeow

729 512 343 216 125 64 27 8 1 0

 

Here, the type of n * n * n is int, so this lambda’s function call operator returns int.

 

 

Lambdas with more complicated compound-statements don’t get automatically deduced return types.  You have to explicitly specify them:

 

C:Temp>type returnmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    deque<double> d;

 

    transform(v.begin(), v.end(), front_inserter(d), [](int n) -> double {

        if (n % 2 == 0) {

            return n * n * n;

        } else {

            return n / 2.0;

        }

    });

 

    for_each(d.begin(), d.end(), [](double x) { cout << x << ” “; });

    cout << endl;

}

 

C:Temp>cl /EHsc /nologo /W4 returnmeow.cpp > NUL && returnmeow

4.5 512 3.5 216 2.5 64 1.5 8 0.5 0

 

The -> double is the optional lambda-return-type-clause.  Why doesn’t it go on the left, like what programmers have been doing with C functions for longer than I’ve been alive?  Because then the lambda-introducer wouldn’t come first, and that’s what tells the compiler that a lambda expression is beginning.  (Figuring out this kind of stuff is what the Core Working Group excels at; trying to imagine whether a given construct would be parseable within C++ hurts my head.)

 

 

If you forget the lambda-return-type-clause, the compiler will complain

Posted in C++

0 comments

Discussion is closed.

Feedback usabilla icon