【问题标题】:Why does the implementation of declval in libstdc++-v3 look so complicated?为什么 libstdc++-v3 中 declval 的实现看起来那么复杂?
【发布时间】:2021-01-11 14:25:46
【问题描述】:

以下代码来自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. 第一个 sn-p 中的模板 1 有什么作用?
  3. 为什么__declval需要一个参数(int/long)?
  4. 为什么模板 1 (int) 和模板 2 (long) 有不同的参数类型?
  5. 我的简单实现有什么问题吗?

【问题讨论】:

    标签: c++ c++11 templates decltype declval


    【解决方案1】:

    std::declval其实是:

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

    其中std::add_rvalue_reference&lt;T&gt; 通常是T&amp;&amp;,除非在无效的情况下(例如T = voidT = int() const),它只是T。主要区别在于函数不能返回数组,但可以返回数组引用,如U(&amp;&amp;)[]U(&amp;&amp;)[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&lt;T&gt; 的返回类型是 decltype(__declval&lt;_Tp&gt;(0))。查找__declval,发现两个函数模板。

    第一个返回类型为_Up = T&amp;&amp;。第二个只是返回类型T

    第一个接受参数int,第二个接受long。它被传递给0,它是一个int,所以第一个函数更匹配并被选中,并返回T&amp;&amp;

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

    如果无法从函数返回 TT&amp;&amp;(例如,T = int() const),则无法选择这两个函数,并且 std::declval&lt;T&gt; 函数出现替换失败且不可行候选人。


    这是引入优化的 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&lt;T&gt;::type

    【讨论】:

    • 但是...为什么第二个模板参数带有默认参数而不是_Tp&amp;&amp;
    • @JohannesSchaub-litb 似乎没有什么特别的原因。 libstdc++ 使用 _Tp&amp;&amp; 返回类型实现它。 libc++ 中的其他类型特征使用未命名的bool = SFINAE_stufftypename = SFINAE_stuff,所以可能是为了保持一致性?没关系
    【解决方案2】:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2021-06-30
      • 1970-01-01
      • 2015-09-25
      • 1970-01-01
      • 2013-06-09
      • 2021-12-12
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      相关资源
      最近更新 更多