48

下面的代码来自 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. 我的简单实现有什么问题吗?
4

2 回答 2

56

std::declval实际上是:

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

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

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

add_rvalue_reference这是通过将 " " 直接内联到declval. 这是使用 SFINAE 完成的。


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

第一个有返回类型_Up = T&&。第二个只有返回类型T

第一个接受一个参数int,第二个接受long。它正在被传递0,它是一个int,所以第一个函数是一个更好的匹配并且被选择,然后T&&被返回。

除非,当T&&不是一个有效类型(例如,T = void),那么当模板参数_Up被推导T&&的替换时,替换失败。所以它不再是该功能的候选者。这意味着只剩下第二个,并且将0转换为 long (返回类型为 just T)。

在不能从函数返回的情况下(例如T),这两个函数都不能被选择,并且该函数存在替换失败并且不是可行的候选者。T&&T = int() conststd::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

于 2020-09-25T11:50:49.620 回答
18

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

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

您的实现不会添加引用,这会因数组和函数而失败。

test::declval<void()>() // fails
于 2020-09-25T09:11:00.860 回答