25

Is declval<T>() just a replacement for the old trick of (*(T*)NULL) to get an instance of T in a decltype without needing to worry about T's constructor?

Here is some sample code:

struct A {};

struct B {
    A a;
};

typedef decltype((*(B*)nullptr).a) T1;
typedef decltype(declval<B>().a) T2;

cout << "is_same: " << is_same<T1, T2>::value << endl;

which prints 1 because T1 and T2 are the same type.

If declval is more than a replacement, what are the differences and where is it useful?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
walrii
  • 3,472
  • 2
  • 28
  • 47

2 Answers2

31

declval() has the advantage that if it is used in an evaluated context (i.e., odr-used) then the program is ill-formed (20.2.4p2), and a diagnostic is required to be issued (per 1.4p1). Typically this is enforced through a static_assert in the library:

c++/4.7/type_traits: In instantiation of '[...] std::declval() [...]':
source.cpp:3:22:   required from here
c++/4.7/type_traits:1776:7: error: static assertion failed: declval() must not be used!

declval also works on reference types:

using S = int &;
using T = decltype(std::declval<S>());
using U = decltype(*(S *)nullptr);  // fails

Where the type is not a reference type, declval will give an rvalue type where nullptr gives an lvalue.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 2
    Just to make sure I understand your first point: Using declval outside of a decltype will cause a compile error where using (\*(T\*)NULL) in the same place would cause no complaints at compile time and a segfault at runtime. decltype is clearly better. – walrii Feb 02 '13 at 00:34
  • 3
    @walrii that's right, except that a segfault at runtime is not guaranteed. – ecatmur Feb 02 '13 at 16:57
  • Great answer ! Lovely ! – Anwesha Dec 20 '15 at 07:25
  • 2
    Note that this is not always strictly an advantage; for instance, if for some reason you need to know the address offset of a class-member, [the casting-a-literal trick will allow you to determine this without constructing an actual class object](http://stackoverflow.com/q/36165639/1858225), but `declval` will not. – Kyle Strand Mar 22 '16 at 22:10
  • @Kyle Strand that trick has undefined behavior, so it's immaterial whether or not you could achieve the same result with declval; in fact, you could if you can guarantee that the call to declval will not be odr-used. To find the offset of a class member you should be using offsetof anyway. – ecatmur Mar 22 '16 at 23:12
  • 1
    @ecatmur As far as I know, there's no standard way to get the offset for a non-POD or non-standard-layout type, which is probably why AOSP uses the code they do even though it's technically UB. – Kyle Strand Mar 23 '16 at 00:37
  • 2
    Is there a more confusing message than " error: static assertion failed: declval() must not be used!"? The average programmer will complain "why does it exist if I can't use it". They probably mean "must not be odr-used", or should completely change that message IMO. – Johannes Schaub - litb May 23 '17 at 14:18
8

No, declval<T>() is not the same as (*(T*)nullptr). And decltype(expr.bar) is not the same as decltype((expr.bar)).

The former comparison compares expressions. The latter use of decltype inspects the expression and the former use of decltype inspects the declared type of expr.bar. So you have to paren your uses of the decltype operand to make a useful comparison of the types and you will find they are different.

struct A {};

struct B {
    A a;
};

// E1: B().a 
// E2: declval<A>().a
// E3: (*(B*)0).a
// E4: ((B&&)(*(B*)0)).a

In these 4 expressions, all expressions have type A. E1 is a prvalue (in C++14 it is an xvalue. Some compilers probably will treat it as an xvalue even in their C++11 mode), E2 is an xvalue. E3 is an lvalue and E4 is an xvalue again.

// T1: decltype((*(B*)0).a)
// T2: decltype(((*(B*)0).a))

In these two types, the first decltype gives the type of the member named by the expression. The member has type A, so T1 is A. The second decltype yields the type of the expression, modified by & if the expression is an lvalue and modified by && if the expression is an xvalue. The expression is an lvalue, so T2 is A&.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212