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

问题描述

以下代码来自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. 我的简单实现是否存在任何问题?

解决方法

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。主要区别在于函数无法返回数组,但可以返回诸如U(&&)[]U(&&)[N]之类的数组引用。

显式使用std::add_rvalue_reference的问题在于它实例化了模板。在libstdc ++实现中,它本身以〜4的实例化深度实例化了大约10s的模板。在通用代码中,std::declval可以被大量使用,并且根据https://llvm.org/bugs/show_bug.cgi?id=27798的说法,不使用std::add_rvalue_reference可以将编译时间提高4%以上。 (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类型(返回类型仅为T)。

在无法从函数(例如,T)返回T&&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

,

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

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

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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...