为什么as_const禁止右值参数

Why does as_const forbid rvalue arguments?

本文关键字:值参 参数 禁止 const as 为什么      更新时间:2023-10-16

我想问为什么as_const禁止右值参数,根据cppreference.com(即为什么标准人员这样做,而不是为什么cppreference.com特别引用他们)。也不是在规范中委员会的意图被编纂,只是为了确保:):)。这个(人为的)示例将产生一个错误(用户希望将其设置为const以保持COW安静)

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

另一个问题的答案指出,如果我们只是删除右值引用重载的删除,它将默默地将右值转换为左值。对,但是为什么不优雅地处理右值,为右值输入返回const右值,为左值输入返回const左值呢?

问题是处理生命周期延长

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);
}

涉及额外的结构,可能还有其他陷阱。

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

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

并且可能没有令人信服的用例来证明忽略这种可能性。

(我误读了本问答的一个相关问题,不小心回答了错误的问题;我将把我的答案转移到这个问题上,这个问题实际上是我的答案所要解决的)

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

第九。进一步讨论

上面的实现只支持安全地将l值重转换为Const(即使它可能已经是Const)。很可能期望有x值和右值也可用于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捕获的临时对象的扩展问题,但是我们还没有完全研究这些形式的所有含义。我们我们愿意扩大这个提议的范围,但我们觉得一个简单易用的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::矢量矢量;(auto&和;val: std::as_const(getVector())) {…}

std::rangesoperator |std::as_const() 都有a删除了对右值的重载,以禁用此功能和类似用途。提议的解决方案是可能的。我们绝对可以讨论这些例子的可用性,,但似乎有问题导致=delete的例子比我们想象的要多

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

因为as_const不接受实参作为const引用。非const左值引用不能绑定到临时值。

明确删除转发引用过载