【发布时间】:2021-08-21 20:38:59
【问题描述】:
标准库实用程序declval 是defined:
template<class T> add_rvalue_reference_t<T> declval() noexcept;
在此处添加 右值引用 似乎是个好主意,如果您考虑一下在 C++11 中引入的语言:返回值涉及临时的,后来被搬走了。 现在C++17引入了保证复制省略,这不再适用。正如cppref 所说:
纯右值和临时值的 C++17 核心语言规范是 与早期的 C++ 版本根本不同:有 不再是临时复制/移动的。另一种描述方式 C++17 机制是“非物化值传递”:prvalues 是 在没有实现临时性的情况下返回和使用。
这会对根据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 类型特征表明这是不可能的,因为就declval 而言它是defined:
模板特化的谓词条件 当且仅当满足
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)标准中的缺陷吗?还是我错过了为什么这些 declval、is_constructible 和 is_invocable 规范仍然是我们可以拥有的最佳解决方案?
【问题讨论】:
-
Class c2(c1);失败。为什么is_constructible说不呢?在您的示例中,您根本没有构建新对象。 -
@AyxanHaqverdili
Class c2(c1)正在尝试从Class&(或Class const&)构建。这将被is_constructible正确拒绝 -
@Tobi
c1的类型是Class,而不是Class&。 -
@AyxanHaqverdili,变量
c1的类型是。但是表达式的类型是一个引用。
标签: c++ c++17 copy-elision return-value-optimization declval