std::d eclval 是否因为保证复制而过时?



标准库实用程序declval定义为:

template<class T> add_rvalue_reference_t<T> declval() noexcept;

如果您考虑一下C++11中引入的语言,在这里添加rvalue 引用似乎是一个好主意:返回值涉及一个临时值,随后从中移动。 现在C++17引入了保证复制省略,这不再适用。正如cppref所说:

C++17 prvalues 和临时性的核心语言规范是 与前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>()...);

因此,在我们的示例中,类型特征声明是否可以从返回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的prvalue创建一个T。这不是使用declval的怪癖;这就是is_constructible问题的含义。

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

std::declval函数主要用于转发。下面是一个示例:

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

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

在 C++23 中,随着std::reference_converts_from_temporary/std::reference_constructs_from_temporary的加入,现在优先将T为prvalue,T&为左值,T&&为类型特征中的xvalue。

它在标准中以VAL<T>来定义,如果T是引用类型,否则是T类型的prvalue,则基本上是declval<T>()

这个VAL<T>declval_cpp17<T>()非常相似。这在您提到的示例中很有用。

但是,std::is_constructible_v的定义永远不能更改为是否编译T t(VAL<Args>...);。现有呼叫站点过多,如下所示:

template<typename... Args> requires(std::is_constructible_v<T, Args...>)
void construct(Args&&... args);

必须将其更改为is_constructible_v<T, Args&&...>才能正确。

我相信add_rvalue_reference_t存在于declval中,因为它旨在与完美转发一起使用。 即,std::forward<T>(expr)将具有与std::declval<T>()相同的类型和值类别。这应该一直std::declval<T&&>(),但现在无法更改。

相关内容

  • 没有找到相关文章

最新更新