I'm looking at the isocpp.org FAQ on C++14 language extensions, reading about decltype(auto)
:
...
Note:
decltype(auto)
is primarily useful for deducing the return type of forwarding functions and similar wrappers, as shown above, where you want the type to exactly “track” some expression you’re invoking. However, decltype(auto) is not intended to be a widely used feature beyond that. In particular, although it can be used to declare local variables, doing that is probably just an antipattern since a local variable’s reference-ness should not depend on the initialization expression. Also, it is sensitive to how you write the return statement. These two functions have different return types:decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; } decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); }
The first returns string, the second returns
string &
, which is a reference to the local variablestr
.
My question: Shouldn't the return types in the example be the other way around, I mean, the parentheses should form an expression whose type should be either a non-reference (or an rvalue-reference?); and without the parentheses, saying str
means "lvalue reference to str
". Am I wrong?
Yes the parentheses form an expression. Expressions are not of reference type, ever.
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
"reference to T" means both lvalue and rvalue reference. But the expression does have a value category. str
as an expression is an lvalue, and so is (str)
since parentheses preserve type and value category.
Since the decltype
is applied to a lvalue expression in the second case, it produces a lvalue reference.
In the first case, the special pleading rule for id-expressions returns the "type of the entity", which is a value type.
(@lubgr pointed out a relevant answer to another question)
The language specification says:
If the argument is either the unparenthesised name of an object/function ... then the decltype specifies the declared type of the entity specified by this expression.
If the argument is any other expression of type T, then ... b) if the value category of expression is lvalue, then the decltype specifies T& ... Note that if the name of an object is parenthesised, it becomes an lvalue expression, thus decltype(arg) and decltype((arg)) are often different types.
So, the non-parenthesized case is an exceptional/special case, apparently introduced to facilitate returning references with decltype(auto); without the special-casing, the rule @StoryTeller quotes is in effect:
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
and this would have make it difficult to return references.
Definitely ranks up there with other magick definitions such as the destruction order of tuples or empty strings having 0 at index 0, temporary lifetime extension with references and other similar wonders...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With