The New C++, Part 2 - decltype

Working with templates is annoying. Working with return types deduced from template arguments is even more annoying. Things quickly tend to spiral out of control, frequently requiring the introduction of typedef's just to make the code marginally more readable. More importantly though, its very difficult to get the TYPE of an expression out of an expression without deducing it statically. This isn't actually much of a problem with the introduction of auto. The problem comes in, though, when you wish to declare the return type of a function dependent upon the result of an expression.

Motivational Studies

This provided one of the main motivating factors for the introduction of the decltype operator in C++11. Another reason for the existence of decltype is for the purposes of providing perfect forwarding. Something that is a necessity for rvalue-references, and move semantics. It is also useful though in other ways. A very trivial example of the problems with forwarding without decltype is demonstrated as follows:

The problem here is simple, do_something simply isn't forwarding the return type properly. For the float version of foo, do_something should be returning a constant reference to a float, whereas it simply returns a float. You cannot simply solve this by changing the return to a constant reference either, because then you're returning a reference to an unnamed temporary when you call the integer version of foo.

The question thus becomes: How can we write a do_something such that it returns the exact same type as the function it forwards to? Well, to start off with, we need to change up the format of the function a wee bit. The biggest issue we have at the moment is that the return value is clearly going to be determined by the invocation of foo against the parameter passed to do_something, however at the point that the return type is specified we do not even yet KNOW of the parameter to do_something:

template<class T>
T const& do_something(T const& t) {
^ ^--- We do not know about this
|-----------------At this point

So we clearly need some way to re-arrange it so that we can know about things like the parameters to the function. Here enters the new result type specification, or as the standard calls it, late-specified return type...

template<class T>
UNKNOWN const& do_something(T const& t) -> T {

clearly though we're still missing something, as we need to specify a placeholder for the return type. Thankfully we have such a something, its called auto.

template<class T>
auto const& do_something(T const& t) -> T {

Ok, so we've gone to all this work of re-arranging our return type so that we can know about the function parameters... we still don't yet have a way to figure out what the return type should actually be, and now we bring in decltype.

The decltype operator returns the exact type of an expression. Its important to understand that the type returned is a fully qualified type, down to cv-qualifiers. Much like the sizeof operator, the expression passed to the decltype operator is not evaluated except statically to determine its resulting type. This very useful, because you can pass parameters to get the type you require out of an expression without having to worry about ensuring that the parameters are actually valid. An example would be passing in null pointers to a function which expects non-null pointers. Thus we can now redefine do_something as such:

Auto has clearly made a big difference, and it is much easier to read, however we still have the small problem of that return type, which is ridiculously long and rather hard to actually read. Not to mention, we're hard coding the iterator type we're returning, something that's usually not very useful. With the trivial application of decltype we can eliminate a large chunk of unreadable code by reducing it down to a simple expression:

It is important to note that decltype is not just restricted to return types. In fact, decltype can be used to declare variables, although it generally makes more sense to use auto... unless you need the exact result type of the expression. More on that when we get to rvalue-references though.