问题描述
以下代码来自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
)具有不同的参数类型? - 我的简单实现是否存在任何问题?
解决方法
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
。主要区别在于函数无法返回数组,但可以返回诸如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
重载。如果_Tp
为void
,则int
重载将失败_Up = void&&
,然后选择long
重载。
您的实现不会添加引用,而引用会因数组和函数而失败。
test::declval<void()>() // fails