标准库实用程序declval
定义为:
template<class T> add_rvalue_reference_t<T> declval() noexcept;
如果您考虑在C++11中引入该语言时在此处添加右值引用似乎是一个好主意:返回值涉及一个临时值,该值随后被移出。现在C++17引入了保证复制省略,这不再适用。正如cppref所说:
C++17 prvalues 和 temporaries 的核心语言规范与早期的 C++ 修订版根本不同:不再有一个临时的可以复制/移动。描述 C++17 机制的另一种方式是“未实现的值传递”:纯右值被返回并使用,而无需实现临时值。
这对根据declval
. 看看这个例子(在godbolt.org上查看):
#include <type_traits>
struct Class {
explicit Class() noexcept {}
Class& operator=(Class&&) noexcept = delete;
};
Class getClass() {
return Class();
}
void test() noexcept {
Class c{getClass()}; // succeeds in C++17 because of guaranteed copy elision
}
static_assert(std::is_constructible<Class, Class>::value); // fails because move ctor is deleted
这里我们有一个不可移动的类。由于保证复制省略,它可以从函数返回,然后在本地实现test()
。然而,is_construtible
类型特征表明这是不可能的,因为它是根据以下定义的declval
:
is_constructible<T, Args...>
当且仅当以下变量定义对于某个发明的变量是格式良好的时,模板特化的谓词条件 才会被满足t
:
T t(declval<Args>()...);
因此,在我们的示例中,类型特征说明 ifClass
可以从返回的假设函数构造Class&&
。test()
尽管命名表明确实如此,但任何当前类型特征都无法预测是否允许输入行is_constructible
。
这意味着,在保证复制省略实际上可以挽救局面的所有情况下,is_constructible
通过告诉我们“它可以在 C++11中构造吗?”的答案来误导我们。
这不限于is_constructible
. 用(在godbolt.org上查看)扩展上面的例子
void consume(Class) noexcept {}
void test2() {
consume(getClass()); // succeeds in C++17 because of guaranteed copy elision
}
static_assert(std::is_invocable<decltype(consume), Class>::value); // fails because move ctor is deleted
这表明is_invocable
同样受到影响。
最直接的解决方案是更改declval
为
template<class T> T declval_cpp17() noexcept;
这是 C++17(及后续,即 C++20)标准中的缺陷吗?还是我错过了为什么这些declval
和 规范仍然是我们可以拥有的最佳解决方案?is_constructible
is_invocable