标准库实用程序declval
是defined,如下所示:
template<class T> add_rvalue_reference_t<T> declval() noexcept;
在这里添加一个 * 右值引用 * 似乎是一个好主意,如果你想一下它在 C++11 中引入时的语言:返回一个值涉及到一个临时的,它随后被从.现在 C++17 引入了 * 保证副本省略 *,这不再适用。正如cppref所说:
C17核心语言的纯右值和临时值规范与早期的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
类型trait表明这是不可能的,因为它是根据declval
定义的:
模板特化is_constructible<T, Args...>
的 predicate 条件应满足,当且仅当以下变量定义对于某个发明的变量t
是良构的:T t(declval<Args>()...);
因此,在我们的示例中,trait类型声明if Class
可以从一个返回Class&&
的假设函数构造。test()
中的行是否被允许不能被任何当前的类型trait预测,尽管命名表明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;
这是C17中的一个缺陷(以及随后的,即C20)标准?或者我错过了一点为什么这些declval
,is_constructible
和is_invocable
规范仍然是我们可以拥有的最佳解决方案?
3条答案
按热度按时间azpvetkf1#
然而,
is_construtible
类型trait表明这是不可能的,因为它是根据declval
定义的:Class
不能从它自己类型的示例中构造。所以is_constructible
不应该说它是。如果一个类型
T
满足is_constructible<T, T>
,则期望你可以在给定一个类型为T
的对象的情况下生成一个T
,* 而不是 * 你可以从一个类型为T
的纯右值生成一个T
。这不是使用declval
的怪癖;这就是问题is_constructible
的意思。你的建议是
is_constructible
应该回答一个与它打算回答的问题不同的问题。应该注意的是,保证省略意味着 * 所有类型 * 都可以从它自己类型的纯右值“构造”。如果这是你想问的,你已经有答案了。xcitsw882#
std::declval
函数主要用于转发。下面是一个例子:在这种常见的情况下,让
std::declval
返回纯右值是错误的,因为没有好的方法来转发纯右值。km0tfn4u3#
在C++23中,随着
std::reference_converts_from_temporary
/std::reference_constructs_from_temporary
的增加,T
是纯右值,T&
是左值,T&&
是类型trait中的xvalue。它在标准中定义为
VAL<T>
,如果T
是引用类型,则基本上是declval<T>()
,否则是T
类型的纯右值。这个
VAL<T>
与declval_cpp17<T>()
非常相似。这在你提到的例子中会很有用。但是,
std::is_constructible_v
的定义永远不能更改为T t(VAL<Args>...);
是否编译。太多现有呼叫站点,看起来像:必须将其更改为
is_constructible_v<T, Args&&...>
才能正确。我相信
add_rvalue_reference_t
存在于declval
中,因为它意味着要用于完美转发。也就是说,std::forward<T>(expr)
将具有与std::declval<T>()
相同的类型和值类别。这应该沿着是std::declval<T&&>()
,但现在不能更改。