简体   繁体   English

由于保证复制省略,std::declval 是否已过时?

[英]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 variable t :模板特化的谓词条件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?或者我错过了为什么这些declvalis_constructibleis_invocable规范仍然是我们可以拥有的最佳解决方案?

However the is_construtible type trait suggests this is not possible, because it is defined in terms of declval :然而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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM