The code below comes from libstdc++-v3 std::type_traits
, which is an implementation of std::declval
:
template<typename _Tp, typename _Up = _Tp&&> // template 1
_Up
__declval(int);
template<typename _Tp> // template 2
_Tp
__declval(long);
template<typename _Tp> // template 3
auto declval() noexcept -> decltype(__declval<_Tp>(0));
But I think I can implement declval
as simply:
template <typename T> T declval();
Here is my test code:
#include <iostream>
using namespace std;
struct C {
C() = delete;
int foo() { return 0; }
};
namespace test {
template <typename T> T declval();
};// namespace test
int main() {
decltype(test::declval<C>().foo()) n = 1;
cout << n << endl;
}
Build and run commands are:
g++ -std=c++11 ./test.cpp
./a.out
- Why does the implementation in libstdc++-v3 look so complicated?
- What does template 1 in the first snippet do?
- Why does
__declval
need a parameter (int
/long
)? - Why do template 1 (
int
) and template 2 (long
) have different parameter types? - Are there any problems with my simple implementation?
std::declval
is actually:Where
std::add_rvalue_reference<T>
is usuallyT&&
, except in cases where that is invalid (Like ifT = void
orT = int() const
), where it is justT
. The main difference is that functions cannot return arrays, but can return array references likeU(&&)[]
orU(&&)[N]
.The problem with explicitly using
std::add_rvalue_reference
is that it instantiates a template. And that itself instantiates around 10s of templates at an instantiation depth of ~4 in the libstdc++ implementation. In generic code,std::declval
can be used a lot, and according to https://llvm.org/bugs/show_bug.cgi?id=27798, there is a >4% compile time boost by not usingstd::add_rvalue_reference
. (The libc++ implementation instantiates less templates, but it still has an impact)This is fixed by inlining the "
add_rvalue_reference
" directly intodeclval
. This is done using SFINAE.The return type for
declval<T>
isdecltype(__declval<_Tp>(0))
. When looking up__declval
, two function templates are found.The first has return type
_Up = T&&
. The second just has return typeT
.The first takes a parameter
int
, and the secondlong
. It is being passed0
, which is anint
, so the first function is a better match and is chosen, andT&&
is returned.Except, when
T&&
is not a valid type (e.g.,T = void
), then when the template argument_Up
is substituted with the deducedT&&
, there is a substitution failure. So it is no longer a candidate for the function. That means only the second one is left, and the0
is converted into a long (And the return type is justT
).In cases where
T
andT&&
can't be returned from a function (e.g.,T = int() const
), neither function can be picked, and thestd::declval<T>
function has a substitution failure and is not a viable candidate.Here is the libc++ commit introducing the optimisation: https://github.com/llvm/llvm-project/commit/ae7619a8a358667ea6ade5050512d0a27c03f432
And here is the libstdc++ commit: https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=ec26ff5a012428ed864b679c7c171e2e7d917f76
They were both previously
std::add_rvalue_reference<T>::type