为什么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
- 为什么libstdc++-v3中的实现看起来那么复杂?
- 第一个代码段中的模板 1 有什么作用?
- 为什么
__declval
需要一个参数(int
/long
)?
- 为什么模板 1 (
int
) 和模板 2 (long
) 的参数类型不同?
- 我的简单实现有什么问题吗?
这是为了捕获无法形成引用的类型。特别是 void
.
通常选择 int
重载。如果 _Tp
是 void
,int
重载将失败 _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 = void
或 T = 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
)。
如果 T
和 T&&
不能从一个函数中 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
以下代码来自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
- 为什么libstdc++-v3中的实现看起来那么复杂?
- 第一个代码段中的模板 1 有什么作用?
- 为什么
__declval
需要一个参数(int
/long
)? - 为什么模板 1 (
int
) 和模板 2 (long
) 的参数类型不同? - 我的简单实现有什么问题吗?
这是为了捕获无法形成引用的类型。特别是 void
.
通常选择 int
重载。如果 _Tp
是 void
,int
重载将失败 _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 = void
或 T = 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
)。
如果 T
和 T&&
不能从一个函数中 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