【问题标题】:Is std::declval outdated because of guaranteed copy elision?由于保证复制省略,std::declval 是否已过时?
【发布时间】:2021-08-21 20:38:59
【问题描述】:

标准库实用程序declvaldefined

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&lt;T, Args...&gt; 对于某些发明,以下变量定义将是格式良好的 变量t:
T t(declval&lt;Args&gt;()...);

因此,在我们的示例中,类型特征说明 Class 是否可以从返回 Class&amp;&amp; 的假设函数构造。 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 规范仍然是我们可以拥有的最佳解决方案?

【问题讨论】:

  • Class c2(c1); 失败。为什么is_constructible 说不呢?在您的示例中,您根本没有构建新对象。
  • @AyxanHaqverdili Class c2(c1) 正在尝试从Class&amp;(或Class const&amp;)构建。这将被is_constructible 正确拒绝
  • @Tobi c1 的类型是 Class,而不是 Class&amp;
  • @AyxanHaqverdili,变量c1的类型是。但是表达式的类型是一个引用。

标签: c++ c++17 copy-elision return-value-optimization declval


【解决方案1】:

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

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

如果T 类型满足is_constructible&lt;T, T&gt;,则期望在给定T 类型的对象的情况下您可以创建T您可以创建T特别是来自T 类型的prvalue。这不是使用declval 的怪癖;这就是问题is_constructible 的意思。

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

【讨论】:

  • 所有类型都是可构造的”在你的回答中非常有说服力......但是对于前面的段落:为什么is_constructible&lt;T,T&gt;不应该在这里表​​示prvalue,我们已经有is_constructible&lt;T,T&amp;&gt;is_constructible&lt;T,T const&amp;&gt; 来表示“从给定对象构造”。这个论点如何推广到is_invocable
  • @Tobi:我认为考虑它的方式是使用纯右值来初始化某些东西根本不是构造
  • @Tobi: "为什么is_constructible&lt;T,T&gt; 不应该是来自prvalue的意思" 因为没有prvalue type这样的东西。表达式是纯右值,但类型不是。
  • IMO declval 应该最接近 decltype 的反转。并给 decltype 一个纯右值,它给出表达式“T”的类型,它周围有 no 右值引用。 IMO 这是 declval 的一个怪癖。 IMO 这与 is_constructible 无关。 is_same_v&lt;T, decltype(declval&lt;T&gt;())&gt; 看起来很受欢迎。
  • @David 尽管它独立于declval 的理想行为,但为什么不应将string s(10) 描述为“构造”字符串?难道连constructor都被调用了吗?
【解决方案2】:

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

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

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

【讨论】:

  • 在这种情况下看到它对我来说很有意义。虽然为什么不在返回类型中写 std::forward 呢? args 在这里可见。感觉它需要一个更好的例子。
  • @JohannesSchaub-litb 是的,我想出了一个显示转发的简单示例。 std::forward 的一个更有用的例子是在类型特征和其他你没有 args... 的地方
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-13
  • 2023-01-30
  • 2016-10-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多