[英]Is std::declval outdated because of guaranteed copy elision?
The standard library utility declval
is defined as:标准库实用程序
declval
定义为:
template<class T> add_rvalue_reference_t<T> declval() noexcept;
To add a rvalue reference here seemed like a good idea, if you think about the language when it was introduced in C++11 : Returning a value involved a temporary, that was subsequently moved from.如果您考虑在C++11中引入该语言时在此处添加右值引用似乎是一个好主意:返回值涉及一个临时值,该值随后被移出。 Now C++17 introduced guaranteed copy elision and this does not apply any more.
现在C++17引入了保证复制省略,这不再适用。 As cppref puts it:
正如cppref所说:
C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from.
C++17 prvalues 和 temporaries 的核心语言规范与早期的 C++ 修订版根本不同:不再有临时复制/移动。 Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
描述 C++17 机制的另一种方式是“未实现的值传递”:prvalues 被返回并使用,而无需实现临时值。
This has some consequences on other utilities implemented in terms of declval
.这会对根据
declval
实现的其他实用程序产生一些影响。 Have a look at this example (view on godbolt.org ):看看这个例子(在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
Here we have a nonmovable class.这里我们有一个不可移动的class 。 Because of guaranteed copy elision , it can be returned from a function and then locally materialised in
test()
.由于保证复制省略,它可以从 function 返回,然后在
test()
中本地实现。 However the is_construtible
type trait suggests this is not possible, because it is defined in terms of declval
:然而
is_construtible
类型特征表明这是不可能的,因为它是根据declval
定义的:
The predicate condition for a template specialization
is_constructible<T, Args...>
shall be satisfied if and only if the following variable definition would be well-formed for some invented variablet
:模板特化的谓词条件
is_constructible<T, Args...>
当且仅当以下变量定义对于某个发明变量t
是格式良好的时才满足:
T t(declval<Args>()...);
So in our example, the type trait states if Class
can be constructed from a hypothetical function that returns Class&&
.因此,在我们的示例中,类型特征说明
Class
是否可以从返回Class&&
的假设 function 构造。 Whether the the line in test()
is allowed cannot be predicted by any of the current type traits, despite the naming suggests that is_constructible
does.尽管命名表明
is_constructible
确实如此,但当前的任何类型特征都无法预测是否允许test()
中的行。
This means, in all situations where guaranteed copy elision would actually save the day, is_constructible
misleads us by telling us the answer to "Would it be constructible in C++11 ?".这意味着,在保证复制省略实际上可以挽救这一天的所有情况下,
is_constructible
通过告诉我们“在 C++11 中可以构造它吗?”的答案来误导我们。
This is not limited to is_constructible
.这不限于
is_constructible
。 Extend the example above with (view on godbolt.org )用(在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
This shows that is_invocable
is similarly affected.这表明
is_invocable
也受到了类似的影响。
The most straightforward solution to this would be to change declval
to最直接的解决方案是将
declval
更改为
template<class T> T declval_cpp17() noexcept;
Is this a defect in the C++17 (and subsequent, ie C++20) standard?这是 C++17(及后续,即 C++20)标准中的缺陷吗? Or am I missing a point why these
declval
, is_constructible
and is_invocable
specifications are still the best solution we can have?或者我错过了为什么这些
declval
, is_constructible
和is_invocable
规范仍然是我们可以拥有的最佳解决方案?
However the
is_construtible
type trait suggests this is not possible, because it is defined in terms ofdeclval
:然而
is_construtible
类型特征表明这是不可能的,因为它是根据declval
定义的:
Class
is not constructible from an instance of its own type. Class
不能从其自身类型的实例构造。 So is_constructible
should not say that it is.所以
is_constructible
不应该说它是。
If a type T
satisfies is_constructible<T, T>
, the expectation is that you can make a T
given an object of type T
, not that you can make a T
specifically from a prvalue of type T
.如果类型
T
满足is_constructible<T, T>
,则期望您可以在给定T
类型的 object 的情况下制作T
,而不是您可以专门从T
类型的纯右值制作T
。 This is not a quirk of using declval
;这不是使用
declval
的怪癖; it is what the question is_constructible
means.这就是问题
is_constructible
的含义。
What you're suggesting is that is_constructible
should answer a different question than the one it is intended to answer.您的建议是
is_constructible
应该回答与它打算回答的问题不同的问题。 And it should be noted, guaranteed elision means that all types are "constructible" from a prvalue of its own type.并且应该注意,保证省略意味着所有类型都可以从其自身类型的纯右值“构造”。 So if that was what you wanted to ask, you already have the answer.
因此,如果这是您想问的,那么您已经有了答案。
The std::declval
function is primarily meant for forwarding. std::declval
function 主要用于转发。 Here's an example:这是一个例子:
template<typename... Ts>
auto f(Ts&&... args) -> decltype(g(std::declval<Ts>()...)) {
return g(std::forward<Args>(args)...);
}
In that common case, having std::declval
returning a prvalue is wrong, since there's no good way to forward a prvalue.在这种常见情况下,让
std::declval
返回纯右值是错误的,因为没有好的方法可以转发纯右值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.