为什么libstdc++-v3中declval的实现看起来这么复杂?

Why does the implementation of declval in libstdc++-v3 look so complicated?

以下代码来自libstdc++-v3 std::type_traits,是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));

但我想我可以简单地实现 declval

template <typename T> T declval();

这是我的测试代码:

#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;
}

构建和 运行 命令是:

g++ -std=c++11 ./test.cpp
./a.out
  1. 为什么libstdc++-v3中的实现看起来那么复杂?
  2. 第一个代码段中的模板 1 有什么作用?
  3. 为什么__declval需要一个参数(int/long)?
  4. 为什么模板 1 (int) 和模板 2 (long) 的参数类型不同?
  5. 我的简单实现有什么问题吗?

这是为了捕获无法形成引用的类型。特别是 void.

通常选择 int 重载。如果 _Tpvoidint 重载将失败 _Up = void&&,然后选择 long 重载。

您的实现没有添加引用,这对数组和函数失败。

test::declval<void()>() // fails

std::declval 实际上是:

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

其中 std::add_rvalue_reference<T> 通常是 T&&,除非在无效的情况下(如 T = voidT = int() const),它只是 T.主要区别在于函数不能 return 数组,但可以 return 数组引用,如 U(&&)[]U(&&)[N].

显式使用 std::add_rvalue_reference 的问题是它实例化了一个模板。在 libstdc++ 实现中,它本身以约 4 的实例化深度实例化了大约 10 个模板。在泛型代码中,std::declval 可以大量使用,根据 https://llvm.org/bugs/show_bug.cgi?id=27798,不使用 std::add_rvalue_reference 可使编译时间提高 >4%。 (libc++实现实例化模板少,但还是有影响的)

这是通过将“add_rvalue_reference”直接内联到 declval 来解决的。这是使用 SFINAE 完成的。


declval<T> 的 return 类型是 decltype(__declval<_Tp>(0))。查找__declval,发现两个函数模板

第一个 return 类型 _Up = T&&。第二个只有 return 类型 T.

第一个接受参数int,第二个long。它被传递 0,这是一个 int,所以第一个函数是更好的匹配并被选择,T&& 是 returned。

除非,当 T&& 不是有效类型时(例如,T = void),则当模板参数 _Up 被推导的 T&& 替换时,有是替代失败。所以它不再是函数的候选。也就是说只剩下第二个了,0转成long(而return类型就是T)。

如果 TT&& 不能从一个函数中 return 编辑(例如,T = int() const),这两个函数都不能被选取,并且std::declval<T> 函数替换失败,不是可行的候选函数。


这是引入优化的 libc++ 提交:https://github.com/llvm/llvm-project/commit/ae7619a8a358667ea6ade5050512d0a27c03f432

这里是 libstdc++ 提交:https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=ec26ff5a012428ed864b679c7c171e2e7d917f76

他们之前都是std::add_rvalue_reference<T>::type