Why does std::declval add a reference?
Asked Answered
I

3

51

std::declval is a compile-time utility used to construct an expression for the purpose of determining its type. It is defined like this:

template< class T >
typename std::add_rvalue_reference<T>::type declval() noexcept;

Would this not be simpler instead?

template< class T >
T declval() noexcept;

What is the advantage of a reference return type? And shouldn't it be called declref?

The earliest historical example I find is n2958, which calls the function value() but already always returns a reference.

Note, the operand of decltype does not need to have an accessible destructor, i.e. it is not semantically checked as a full-expression.

template< typename t >
t declprval() noexcept;

class c { ~ c (); };
decltype ( declprval< c >() ) * p = nullptr; // OK
Intercostal answered 7/9, 2014 at 5:44 Comment(7)
Doesn't T need to be copyable for T to be a valid return type? T && would avoid that.Hebraize
I think it's to support types that can't be return by value, e.g. function type, array type, and abstract class.Machinery
@user2357112: Movability is sufficient.Impala
@Mehrdad Any class type may be a return type, movability is unnecessary.Intercostal
@jrok My example also does not require constructibility.Intercostal
@Potatoswatter: I never said movability is necessary.Impala
@Mehrdad Technically true, but…Intercostal
S
45

The "no temporary is introduced for function returning prvalue of object type in decltype" rule applies only if the function call itself is either the operand of decltype or the right operand of a comma operator that's the operand of decltype (§5.2.2 [expr.call]/p11), which means that given declprval in the OP,

template< typename t >
t declprval() noexcept;

class c { ~ c (); };

int f(c &&);

decltype(f(declprval<c>())) i;  // error: inaccessible destructor

doesn't compile. More generally, returning T would prevent most non-trivial uses of declval with incomplete types, type with private destructors, and the like:

class D;

int f(D &&);

decltype(f(declprval<D>())) i2;  // doesn't compile. D must be a complete type

and doing so has little benefit since xvalues are pretty much indistinguishable from prvalues except when you use decltype on them, and you don't usually use decltype directly on the return value of declval - you know the type already.

Serotonin answered 7/9, 2014 at 6:30 Comment(2)
Ah, I didn't realize the unchecked destructor was a special exception. My example was totally misguided.Intercostal
"no temporary is introduced for function returning prvalue of object type in decltype" turns into "If expression is a prvalue other than a (possibly parenthesized) immediate invocation (since C++20), a temporary object is not materialized from that prvalue." since c++17. en.cppreference.com/w/cpp/language/decltypeAnett
L
12

Arrays cannot be returned by value thus even just the declaration of a function returning an array by value is invalid code.

You can however return an array by reference.

Licastro answered 7/9, 2014 at 6:7 Comment(5)
That's not true, T doesn't have to be copyable or moveable, it even can be incomplete, see coliru.stacked-crooked.com/a/3f387e9e4e0ce190Machinery
@Jamboree: using typedef int A[4] is going to make A f(); invalid code.Licastro
I can't see what this has got to do with declval.Bellina
@jrok: a definition of declval based on a function returning T by value couldn't be used when T is an array.Licastro
I know. But I don't know why you'd ever want to use declval with array types (may just be my lack of imagination).Bellina
L
4

The purpose of decltype() is to have an expression that acts as a valid value of type T, to put it as a T in expressions expecting Ts. The problem is that in C++ a type Tcan be non-copyable, or even non-default-constructible. So using the T{} for that purpose doesn't work.

What decltype() does is to return an rvalue-reference to a T. An rvalue reference should be valid for any type T, so its guaranteed that we have a valid T from an rvalue reference of T, and its guaranteed we can have an rvalue reference to any type T. Thats the trick.

Think about decltype() as "give me a valid expression of type T". Of course its usage is intended for overload resolution, type determination, etc; since its purpose is to return a valid expression (In the syntactical sense), not to return a value. Thats reflected in the fact that std::declval() is not defined at all, its only declared.
If it was defined, we have the initial problem again (We have to construct a value for an arbitrary type T, and thats not possible).

Locoweed answered 7/9, 2014 at 6:26 Comment(1)
1. I didn't propose to use T{}. 2. decltype returns an rvalue or lvalue reference.Intercostal

© 2022 - 2024 — McMap. All rights reserved.