We have learned that C++11 has four different ways of
making the type of an expression available to the programmer:
-
auto can be
used to set the type of a newly declared variable from its initializing
expression. It removes the reference, if any, from the expression's type, then
removes topmost const and volatile qualifiers.
-
decltype can be used in a wider variety of contexts, such as
typedefs, function return types, and even in places where a class name is
expected. There are two different ways in which decltype(expr) can work,
depending on the nature of expr :
-
If
expr is something as simple as a plain variable whose type can be looked up
in the source code, then decltype(expr) is that type.
-
Otherwise,
decltype(expr) is the type of expr with
an extra lvalue reference added for lvalues and an extra rvalue reference
added for xvalues.
-
decltype(auto) is an alternate way of
setting the type of a newly declared variable from its initializing
expression. It uses the type deduction rules of
decltype
instead of those of auto .
If you focus on the the way references are treated in the above, you see that
auto always drops an outermost reference, whereas decltype
either preserves it or even adds one, depending on the circumstances.
At the end of Section 3,
I gave you what I think is a plausible explanation of the rationale behind
the semantics of auto .
The question arises, "What about the rationale
behind the semantics of decltype ? Why does it work the way
it does, specifically with respect to references?"
As with auto , there is probably more than one way to argue. From what
I have read and heard, the final specification of decltype represents
a compromise between two different possible points of view. One point of view
is that decltype should be a way to retrieve and reuse the type of a variable
as declared in the source code: I declared
a variable x of type T at some point in my code.
Now I want to use that type for some other purpose, like making a typedef,
or specify the return type of a function. I don't
want to repeat myself, so give me a way to recover the declared type of
my variable, exactly the way I originally wrote it. If that
declared type has a reference and/or a const or volatile
qualifier on it that I don't want, I'll remove that myself, thank you very much.
This is what Case 1 of the specification of decltype does.
The above way of looking at decltype says nothing about
what should happen when decltype is applied to more complex expressions.
From what we've said
so far, decltype might as well be undefined for complex expressions.
This is where a different point of view comes in, a point
of view that originates in the needs of library writers. They often find themselves
in a situation where the return type of a function needs to be the type of some
expression, typically something that depends on template parameters. They want to
be able to write
auto foo([...]) -> decltype(expr) {
[...]
return expr;
}
where expr could be any expression. Moreover, if
expr is an lvalue and is not local to the function,
they want to return a refefence, so the returned expression can be
assigned to. This is particularly important if the function foo
is some kind of forwarding function. For that to always work properly, there is
really no choice but to give decltype that reference-adding
semantics. The following example, which was provided by Stephan Lavavej,
demonstrates that:
template <typename T>
auto array_access(T& array, size_t pos) -> decltype(array[pos]) {
return array[pos];
}
std::vector<int> vect = {42, 43, 44};
int* p = &vect[0];
array_access(vect, 2) = 45;
array_access(p, 2) = 46;
The last line, where the pointer p is used to access the element,
could not be made to work without having decltype add a reference:
the type of p[2] is int , any way you turn it. There is
no reference in sight here. But p[2] is an lvalue, and by letting
decltype add references to lvalues, we can get the desired effect.
All this comes with a caveat: the reference that decltype adds is not always what
you want. It happens frequently that you
need to remove it with remove_reference . We saw an example of that
in Section 8.
Here are links to some more articles on auto and decltype
that you may find helpful:
-
Wikipedia article "decltype"
-
Using C++11 auto and decltype - Code Synthesis
-
Koenig, Andrew. "A Note About decltype". Dr. Dobb's Journal, July 27, 2011.
-
Visual C++ Team Blog entry "decltype"
|