18

Looking at libstdc++ source code, I found the following declval implementation:

template<typename _Tp, typename _Up = _Tp&&>
_Up __declval(int);  // (1)

template<typename _Tp>
_Tp __declval(long); // (2)

template<typename _Tp>
auto declval() noexcept -> decltype(__declval<_Tp>(0));

This implementation was proposed by Eric Niebler as a compile time optimization: he explains that overload resolution is faster than template instantiation.

However, I can't understand how it works. Specifically:

  1. In (1), why is using _Up better than just returning _Tp&& ?
  2. It seems that the overload (2) is never used. Why is it needed?

How all this prevents template instantiations, as opposed to the most naive implementation:

template<typename T>
T&& declval() noexcept;
Evg
  • 25,259
  • 5
  • 41
  • 83
Igor R.
  • 14,716
  • 2
  • 49
  • 83
  • 2
    1. http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/type_traits?view=diff&r1=269990&r2=269991&pathrev=269991 – Language Lawyer May 20 '19 at 07:23
  • @Language Lawyer great, so libc++ implementation doesn't use that 2nd parameter. Still interesting what was the motivation behind it. – Igor R. May 20 '19 at 08:10
  • I suspect the answer would be the same as the answer to why the trailing return type was used: [It's just stylistic](https://bugs.llvm.org/show_bug.cgi?id=27798#c3). But it is better to ask Eric to be 100% sure. – Language Lawyer May 21 '19 at 14:08

1 Answers1

18

The naive implementation is not fully correct. According to the Standard, declval is defined as ([declval]):

template <class T> add_rvalue_reference_t<T> declval() noexcept;

and for add_rvalue_reference<T> the Standard reads ([meta.trans.ref]):

If T names a referenceable type then the member typedef type names T&&; otherwise, type names T.

An example of a non-referenceable type is void. The second overload will be used in that case thanks to SFINAE.

As to the first question, I don't see any special reason. _Tp&& should work just fine.

Evg
  • 25,259
  • 5
  • 41
  • 83