3

标准库实用程序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_constructibleis_invocable

4

2 回答 2

5

然而,is_construtible类型特征表明这是不可能的,因为它是根据以下定义的declval

Class不能从其自身类型的实例构造。所以is_constructible不应该说是。

如果T满足类型is_constructible<T, T>,则期望您可以使T给定的对象成为类型T而不是您可以T专门从类型的prvalue 中创建a T。这不是使用的怪癖declval;这就是问题的is_constructible意思。

你的建议是is_constructible应该回答一个与它打算回答的问题不同的问题。并且应该注意,保证省略意味着所有类型都可以从其自身类型的纯右值“构造”。因此,如果这是您想问的,那么您已经有了答案。

于 2021-06-03T17:53:20.280 回答
1

std::declval功能主要用于转发。这是一个例子:

template<typename... Ts>
auto f(Ts&&... args) -> decltype(g(std::declval<Ts>()...)) {
    return g(std::forward<Args>(args)...);
}

在那种常见的情况下,std::declval返回一个纯右值是错误的,因为没有很好的方法来转发一个纯右值。

于 2021-06-03T17:56:45.613 回答