// myWidget1's type is Widget
decltype(auto)myWidget2 = cw; // decltype type deduction:
// myWidget2's type is
// const Widget&
But two things are bothering you, I know. One is the refinement to authAndAccess
I mentioned, but have not yet described. Let's address that now.
Look again at the declaration for the C++14 version of authAndAccess
:
template
decltype(auto) authAndAccess(Container& c, Index i);
The container is passed by lvalue-reference-to-non- const
, because returning a reference to an element of the container permits clients to modify that container. But this means it's not possible to pass rvalue containers to this function. Rvalues can't bind to lvalue references (unless they're lvalue-references-to- const
, which is not the case here).
Admittedly, passing an rvalue container to authAndAccess
is an edge case. An rvalue container, being a temporary object, would typically be destroyed at the end of the statement containing the call to authAndAccess
, and that means that a reference to an element in that container (which is typically what authAndAccess
would return) would dangle at the end of the statement that created it. Still, it could make sense to pass a temporary object to authAndAccess
. A client might simply want to make a copy of an element in the temporary container, for example:
std::deque makeStringDeque(); // factory function
// make copy of 5th element of deque returned
// from makeStringDeque
auto s = authAndAccess(makeStringDeque(), 5);
Supporting such use means we need to revise the declaration for authAndAccess
to accept both lvalues and rvalues. Overloading would work (one overload would declare an lvalue reference parameter, the other an rvalue reference parameter), but then we'd have two functions to maintain. A way to avoid that is to have authAndAccess
employ a reference parameter that can bind to lvalues and rvalues, and Item 24explains that that's exactly what universal references do. authAndAccess
can therefore be declared like this:
template // c is now a
decltype(auto) authAndAccess(Container &&c, // universal
Index i); // reference
In this template, we don't know what type of container we're operating on, and that means we're equally ignorant of the type of index objects it uses. Employing pass-by- value for objects of an unknown type generally risks the performance hit of unnecessary copying, the behavioral problems of object slicing (see Item 41), and the sting of our coworkers' derision, but in the case of container indices, following the example of the Standard Library for index values (e.g., in operator[]
for std::string
, std::vector
, and std::deque
) seems reasonable, so we'll stick with pass-by-value for them.
However, we need to update the template's implementation to bring it into accord with Item 25's admonition to apply std::forward
to universal references:
template // final
decltype(auto) // C++14
authAndAccess(Container&& c, Index i) // version
{
authenticateUser();
return std::forward(c )[i];
}
This should do everything we want, but it requires a C++14 compiler. If you don't have one, you'll need to use the C++11 version of the template. It's the same as its C++14 counterpart, except that you have to specify the return type yourself:
template // final
auto // C++11
authAndAccess(Container&& c, Index i) // version
-> decltype(std::forward(c)[i])
{
authenticateUser();
return std::forward(c)[i];
}
The other issue that's likely to be nagging at you is my remark at the beginning of this Item that decltype almost always produces the type you expect, that it rarely surprises. Truth be told, you're unlikely to encounter these exceptions to the rule unless you're a heavy-duty library implementer.
To fully understand decltype
's behavior, you'll have to familiarize yourself with a few special cases. Most of these are too obscure to warrant discussion in a book like this, but looking at one lends insight into decltype
as well as its use.
Applying decltype
to a name yields the declared type for that name. Names are lvalue expressions, but that doesn't affect decltype
's behavior. For lvalue expressions more complicated than names, however, decltype
ensures that the type reported is always an lvalue reference. That is, if an lvalue expression other than a name has type T
, decltype
reports that type as T&
. This seldom has any impact, because the type of most lvalue expressions inherently includes an lvalue reference qualifier. Functions returning lvalues, for example, always return lvalue references.
There is an implication of this behavior that is worth being aware of, however. In
int x = 0;
x
is the name of a variable, so decltype(x)
is int
. But wrapping the name x
in parentheses — “ (x)
” — yields an expression more complicated than a name. Being a name, x
is an lvalue, and C++ defines the expression (x)
to be an lvalue, too. decltype((x))
is therefore int&
. Putting parentheses around a name can change the type that decltype
reports for it!
In C++11, this is little more than a curiosity, but in conjunction with C++14's support for decltype(auto)
, it means that a seemingly trivial change in the way you write a return
statement can affect the deduced type for a function:
decltype(auto)f1() {
int x = 0;
…
return x; // decltype(x) is int , so f1 returns int
}
decltype(auto)f2() {
int x = 0;
…
return (x); // decltype((x)) is int& , so f2 returns int&
}
Note that not only does f2
have a different return type from f1
, it's also returning a reference to a local variable! That's the kind of code that puts you on the express train to undefined behavior — a train you certainly don't want to be on.
The primary lesson is to pay very close attention when using decltype(auto)
. Seemingly insignificant details in the expression whose type is being deduced can affect the type that decltype(auto)
reports. To ensure that the type being deduced is the type you expect, use the techniques described in Item 4.
At the same time, don't lose sight of the bigger picture. Sure, decltype
(both alone and in conjunction with auto
) may occasionally yield type-deduction surprises, but that's not the normal situation. Normally, decltype
produces the type you expect.
This is especially true when decltype
is applied to names, because in that case, decltype
does just what it sounds like: it reports that name's declared type.
Читать дальше