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

问题描述

标准库实用程序 declvaldefined 为:

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

这里我们有一个 nonmovable 类。由于保证复制省略,它可以从函数返回,然后在 test() 中本地实现。然而 is_construtible 类型特征表明这是不可能的,因为它是 defineddeclval:

模板特化的谓词条件 is_­constructible<T,Args...> 应满足当且仅当 以下变量定义对于某些发明的人来说是格式良好的 变量 t:
T t(declval<Args>()...);

因此在我们的示例中,类型特征声明是否可以从返回 Class 的假设函数构造 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)标准中的缺陷吗?或者我是否忽略了为什么这些 declvalis_constructibleis_invocable 规范仍然是我们可以拥有的最佳解决方案?

解决方法

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

Class 不是可构造的从它自己类型的实例。所以is_constructible不应该说它是。

如果类型 T 满足 is_constructible<T,T>,则期望您可以在给定类型为 T 的对象的情况下创建 T您可以专门从 T 类型的纯右值生成 T。这不是使用 declval 的怪癖;这就是问题 is_constructible 的意思。

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

,

std::declval 函数主要用于转发。举个例子:

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

在这种常见情况下,让 std::declval 返回纯右值是错误的,因为没有好的方法可以转发纯右值。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...