由于保证复制省略,std::declval是否已过时?
标准库实用程序declval被定义为:
template<class T> add_rvalue_reference_t<T> declval() noexcept;
在这里添加右值引用似乎是一个好主意,如果您考虑一下C++11 中引入的语言:返回值涉及一个临时值,该值随后被移出。现在C++17引入了保证复制省略,这不再适用。正如cppref所说:
纯右值和临时值的 C++17 核心语言规范与早期 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规范仍在我们可以有最好的解决方案?
回答
然而,
is_construtible类型特征表明这是不可能的,因为它是根据以下定义的declval:
Class不能从它自己类型的实例构造。所以is_constructible不应该说它是。
如果类型T满足is_constructible<T, T>,则期望您可以创建一个T给定类型的对象T,而不是您可以T从类型的纯右值中创建一个特定的对象T。这不是使用的怪癖declval;这就是问题的is_constructible意思。
你的建议是,它is_constructible应该回答一个与它打算回答的问题不同的问题。应该注意的是,保证省略意味着所有类型都可以从其自身类型的纯右值“构造”。所以如果这就是你想问的,那么你已经有了答案。
- @Tobi: I think the way to think about it is that use of a prvalue to initialize something is not *construction* at all.