【问题标题】:Why does as_const forbid rvalue arguments?为什么 as_const 禁止右值参数?
【发布时间】:2016-12-27 08:20:54
【问题描述】:

我想问一下为什么as_const 禁止右值参数,according to cppreference.com(即为什么标准人员这样做,而不是为什么 cppreference.com 专门引用他们。而且规范中没有委员会的意图已编纂,只是为了确保:)))。这个(人工)示例会产生错误(用户希望将其设为 const 以保持 COW 安静)

QChar c = as_const(getQString())[0];

Another question's answer 指出,如果我们只是删除右值引用重载的删除,它会默默地将右值转换为左值。对,但为什么不优雅地处理右值,为右值输入返回 const rvalues,为左值输入返回 const lvalues?

【问题讨论】:

  • 谢谢,是的,这是骗子。尽快关闭
  • 啊,我对那里给出的答案不满意..重新打开!请在问题中找到我的笔记
  • 为什么不这样stackoverflow.com/a/9453435/34509 ..我当时也没有看到任何直接的危险。
  • @Mehrdad 我认为上面的 QChar 示例是一个用例。它防止在右值上调用非常量成员函数。我认为,对于 COW 类,即使有一个 ref 限定符也不足以防止 COW-ing。唯一安全的方法是使用 const 重载。而as_const 正好可以支持这一点。即使看起来没有用例,支持它有什么危险?如果真的有危险,那为什么不同时禁止static_cast<const Foo&&>(getFoo())呢?
  • (对不起,删除了我的评论,因为我基本上把它放到了答案中。)但我的意思是:为什么要阻止在右值上调用非常量方法?比如,它会试图防止什么错误?能举个例子吗?

标签: c++ rvalue c++17


【解决方案1】:

问题是处理寿命延长

const auto& s = as_const(getQString()); // Create dangling pointer
QChar c = s[0]; // UB :-/

可能是以下重载(而不是删除的重载)

template< typename T >
const T as_const(T&& t) noexcept(noexcept(T(std::forward<T>(t))))
{
    return std::forward<T>(t);
}

这涉及额外的建设,可能还有其他陷阱。

【讨论】:

  • Johannes 对as_const_ref 的建议可以解决这个问题,不是吗?那么人们就不会期望延长寿命了。
  • @Cheersandhth.-Alf:我关心的是延长寿命,事实上,如果我们处理没有延长寿命要求的案例,as_const_ref 似乎不错。
  • 我认为你的替代方案应该是 std::move(t)?
  • @user541686: std::forward&lt;T&gt;(t) 已使用。
  • 哦,好电话。我没有注意到 t 可能是这里的左值引用!
【解决方案2】:

一个原因可能是由于缺乏所有权转移,它在右值上可能很危险

for (auto const &&value : as_const(getQString()))  // whoops!
{
}

并且可能没有令人信服的用例来证明无视这种可能性是合理的。

【讨论】:

  • 嗯,这似乎是引入危险的一个引人注目的例子!谢谢。让它为右值工作会非常有用,但我可以理解他们不希望人们走进这个陷阱。现在我看到 std::cref 对于右值参数也被删除了,也许与它的对称性是另一个参数?
  • @JohannesSchaub-litb:嗯,当然,但这可能是因果倒退了......我想这也可能是由于同样的原因。
  • “缺乏所有权转移”,这是什么意思?我猜QString 是具有脆弱语义的 COW 实现,其中非const 项目访问会导致预防性复制,const 访问不会但返回引用(这是非常不安全、草率的设计,但我假设它)。我仍然看不到这个例子是关于什么的。我可以想象存储一个指向 value 所指的任何内容的指针,一个临时的或字符串缓冲区中的项目,并且该指针变得非常快。但这与as_const 无关。还是这样?
  • 哦,对const的右值引用是绑定引用的,所以临时字符串的生命周期没有延长。我被“缺乏所有权转让”的评论误导了。这是什么意思?
【解决方案3】:

(在误读后,我不小心回答了此问答的错误问题to a related question;我正在将我的答案转移到这个问题,我的答案实际解决的问题)

P0007R1 作为 C++17 的一部分引入了 std::as_const。被接受的提案根本没有提到右值,但它的先前修订版P0007R0 包含对右值的结束讨论[emphasis mine]:

九。进一步讨论

上述实现仅支持安全地将左值重铸为 const(即使它可能已经是 const)。大概是 希望有 xvalues 和 prvalues 也可以与 as_const 一起使用, 但有一些问题需要考虑。

[...]

支持所有形式的替代实现 上面使用的是:

template< typename T >
inline const T &
as_const( const T& t ) noexcept
{
    return t;
}

template< typename T >
inline const T
as_const( T &&t ) noexcept( noexcept( T( t ) ) )
{
    return t;
}

我们相信这样的实现有助于处理生命周期 as_const 捕获的临时对象的扩展问题,但是 我们还没有完全研究这些形式的所有含义。我们 对扩大本提案的范围持开放态度,但我们认为 即使没有 扩展语义。

所以std::as_const 基本上只为左值添加,因为最初的提案没有完全检查为右值实现它的含义,即使至少访问了右值参数的按值重载返回。另一方面,最终提案侧重于将实用程序用于左值的常见用例。

P2012R0旨在解决基于范围的for循环的隐患

修复基于范围的 for 循环,Rev0

基于范围的for循环成为最重要的控制结构 现代C++。它是处理 a 的所有元素的循环 容器/集合/范围。

但是,由于它当前的定义方式,它可以很容易地 在不平凡但简单的应用程序中引入生命周期问题 由普通应用程序员实现。

[...]

症状

在遍历元素时考虑以下代码示例 集合的元素:

std::vector<std::string> createStrings(); // forward declaration
…
for (std::string s : createStrings()) … // OK
for (char c : createStrings().at(0)) … // UB (fatal runtime error)

虽然迭代临时返回值可以正常工作,但 迭代 对临时返回值的引用是未定义的行为。

[...]

问题的根本原因

上述未定义行为的原因是根据 当前规范,内部循环的范围基础已扩展 多个语句:[...]

以及下面的循环调用:

for (int i : createOptInts().value()) … // UB (fatal runtime error)

定义为等价于以下内容:

auto&& rg = createOptInts().value(); // doesn’t extend lifetime of returned optional
auto pos = rg.begin();
auto end = rg.end();
for ( ; pos != end; ++pos ) {
 int i = *pos;
 …
 } 

根据规则,在初始化期间创建的所有临时值 未直接绑定到它的引用rg 在之前被销毁 原始 for 循环开始。

[...]

问题的严重性

[...]

作为由此问题引起的限制的另一个示例,请考虑 在基于范围的 for 循环中使用 std::as_const()

std::vector vec; for (auto&& val : std::as_const(getVector())) { … }

std::rangesoperator |std::as_const() 都有 删除了右值的重载以禁用此和类似用途。 建议的修复方法是可能的。我们绝对可以 讨论此类示例的可用性,但似乎有 比我们想象的更多的例子问题导致=delete 函数调用右值。

这些陷阱是避免允许右值使用 std::as_const() 重载的一个论据,但如果 P2012R0 被接受,则可以说可以添加这样的重载(如果有人提出建议并展示了有效的用例)。

【讨论】:

    【解决方案4】:

    因为 as_const 不将参数作为 const 引用。 非 const 左值引用不能绑定到临时对象。

    转发引用重载被显式删除。

    【讨论】:

      猜你喜欢
      • 2021-04-19
      • 1970-01-01
      • 1970-01-01
      • 2016-04-06
      • 2019-04-26
      • 2012-02-17
      • 1970-01-01
      • 2018-08-06
      • 2021-09-09
      相关资源
      最近更新 更多