C++0x – Viable?

This is a bit of a selfish post.  I am doing a preliminary investigation into a subset of C++0x features, and I felt like consolidating my thoughts somewhere.  These are not authoritative, expert opinions.  This is me fumbling around the long, drawn-out history of the standard to try to discern the practicalities.  Our main goals are taking features that will reduce complexity and understanding in our code base, and be efficient in terms of memory, performance, compile times and load times.

Null Pointer

Let’s start with the easy one.  This is designed to rid function overloads of the ambiguity between NULL, 0, and 0L, so calling f(0) won’t call f(int) if you want f(void*).  Seems pretty simple and low risk.

Auto/Decltype

Without getting into the template debate, assuming your codebase has some amount of templated code, auto and decltype are designed to help the work the programmer has to do to figure out the return type of a method, most notably in template code.  In these cases, the compiler can determine the return type fairly easily through semantic analysis.  auto is like .NET var, in that it is not a variant, but instead its’ type is deduced at compile time.  It can also help reduce the use of typedef.  The two cases I’ve found useful are:

for (auto iter = vec.begin(); iter != vec.end(); ++iter) // avoids having to type std::vector<int>::iter or typedef it.
 
  auto a = library::really_complicated_method<ClassType1, ClassType2>(const ClassType1& a, int b);  // relies on the code being able to use a without knowing its type explicitly.

decltype seems to be generally usable in conjunction with auto, and lets you declare the type of a variable to be the same as another variable:

auto a = 5;

decltype(a) b = a; // b is of type int

We’ll probably limit this to internal use in our container and algorithm libraries, as they are the only heavily templated code in our engine, and it relies on consistency in API and usage.

Static Assert

I’ve seen this implemented in multiple engines I’ve worked on in the past.  The earlier an error is caught the better, so if something can be verified at compile time, use static_assert:

static_assert(condition, error message).

Verifying data size and template arguments seem like logical uses as they are compile-time entities.

Move Semantics and RValue

This seems like a useful optimization for reducing memory allocations incurred when converting RValues returned from functions into an LValue to assign it to a variable.  A good use case is a vector, which is expensive to copy.  When returning a temporary vector from a function, a new vector must be created and all the data from the temporary must be copied into it.  C++0x allows the programmer to define a “move constructor” akin to the “copy constructor” which allows the programmer to “move” the data (ie. point the new LValue to the temporary).  The syntax for move semantics is std::vector<T>&&.  See Julien’s post for an example of implementation for non-C++0x compilers.

Lambda

Lambda functions are a common sight in functional languages, where functions are first class card carrying members, meaning they can be passed around in the same manner as variables.  In C/C++ similar concepts have been used including function pointers, functors, boost::bind and the like.  The main concept keeping these from elevating methods to first class status is anonymity.  Lambda functions are just that – a syntax to allow passing anonymous functions as parameters.  The basic syntax is as such:

[closure]  (parameter, list) -> return type { function; expressions; return e; }

And here are 3 simple examples:

[] (int x, int y) -> int { return x+y;};
 
  [] (int x, int y) { return x+y;};
 
  [mPosition&, mRotation] (const COORD3& offset) { mPosition += offset; };

The first two examples are identical, but the 2nd shows that if the return type can be deduced, it can be omitted from the declaration.  This deduction can only happen if there is a single return statement in the lambda body.  The return type is defined to be decltype(x+y).

For the variables defined in the closure, the following is valid:

[x] – x is captured by value.

[x&] – x is captured by reference.

[&] – any external variable in scope that is used is done by reference.

[=] – any external variable in scope that is used is done by value.

How can we use these?  The most obvious place is when passing predicates into algorithms:

std::for_each(i.begin(), i.end(), [(*i)&]() {++(*i);}); // increments member of the range.

Lambdas can be assigned to variables via auto:

auto l = [] (int x, int y) { return x+y;}

Lambdas can also be ridiculous:

[](){}(); // creates an empty lambda that has no closure, takes no parameters, returns nothing, and invokes it immediately

Conclusion

While nullptr, auto, decltype, static_assert and move semantics don’t raise any red flags for me in terms of performance or code complexity, lambda functions definitely do.  In a language that supports them natively such as most functional languages where their use is elegant, fluid and natural, in a complex, nasty-looking language like C++, they look a bit like shoe-horning a cactus into a briar patch.  C++0x is supposed to make the language less “experty” and more easily accessible to newcomers, and the lambda syntax seems like it might overwhelm the average programmer.  Having [](){}(); be a valid construct seems like just the tip of the iceberg.  And although the compiler should just generate the same code you’d have to write prior to C++0x, as we know, implicitly generated code can’t always be trusted, so letting teams run wild with them might be something to be concerned about.

This post is of an investigative nature.  I hold no opinions yet about the viability of these features.  My next step is to look under the hood put together use cases, and check out how the various compilers we use deal with non-trivial cases.  Any pointers/tips would be appreciated!

PDF