【问题标题】:Why does the standard allow a tuple of rvalue references to be assigned to by a tuple of lvalue references?为什么标准允许由左值引用元组分配右值引用元组?
【发布时间】:2016-04-05 05:04:43
【问题描述】:

似乎包含一个或多个引用的std::tuple 在构造和分配(尤其是复制/移动构造和复制/移动分配)方面具有意外行为。它不同于std::reference_wrapper(更改引用的对象)和具有成员引用变量的结构(删除了赋值运算符)的行为。它允许方便的std::tie python 像多个返回值,但它也允许明显不正确的代码,如以下(link here)

#include <tuple>

int main()
{
    std::tuple<int&> x{std::forward_as_tuple(9)}; // OK - doesn't seem like it should be
    std::forward_as_tuple(5) = x; // OK - doesn't seem like it should be
    // std::get<0>(std::forward_as_tuple(5)) = std::get<0>(x); // ERROR - and should be
    return 0;
}

该标准似乎在最新工作草案的复制(ish)分配部分20.4.2.2.9 中要求或强烈暗示此行为(Ti&amp; 将折叠为左值参考):

template &lt;class... UTypes&gt; tuple&amp; operator=(const tuple&lt;UTypes...&gt;&amp; u);

9 要求: sizeof...(Types) == sizeof...(UTypes)is_assignable&lt;Ti&amp;, const Ui&amp;&gt;::value 对所有 i 都是 true。

10 作用:将u的每个元素赋值给*this的对应元素。

11 返回: *this

虽然移动(ish)构造部分 20.4.2.1.20 不太清楚(is_constructible&lt;int&amp;, int&amp;&amp;&gt; 返回 false):

template &lt;class... UTypes&gt; EXPLICIT constexpr tuple(tuple&lt;UTypes...&gt;&amp;&amp; u);

18 要求: sizeof...(Types) == sizeof...(UTypes)

19 作用:对于所有i,构造函数将*thisith元素初始化为std::forward&lt;Ui&gt;(get&lt;i&gt;(u))

20 备注:除非is_constructible&lt;Ti, Ui&amp;&amp;&gt;::value对所有i都为真,否则此构造函数不应参与重载决议。当且仅当is_convertible&lt;Ui&amp;&amp;, Ti&gt;::value 对于至少一个i 为假时,构造函数为explicit

这些不是唯一受影响的小节。

问题是,为什么需要这种行为?另外,如果该标准的其他部分在起作用,或者我有误解,请解释我哪里出错了。

谢谢!

【问题讨论】:

  • 对此进行了更多研究。似乎包含右值引用的std::tuple 不应该支持赋值(尽管标准没有提到这一点),并且来自std::tuple&lt;RVALUEREF&gt;std::tuple&lt;LVALUEREF&gt; 构造可能是libc++ 中的一个错误。任何输入都是有价值的,因为我正在实现一个元组变体。
  • libc++ 是正确的,并且已经有一段时间了。请参阅霍华德的回答。

标签: c++ c++11 assignment-operator stdtuple


【解决方案1】:

构造函数

让我们从构造函数开始(你的第一行):

我们都知道(只是澄清一下),decltype(std::forward_as_tuple(9))std::tuple&lt;int&amp;&amp;&gt;

还要注意:

std::is_constructible<int&, int&&>::value == false

在所有编译器平台上。因此,根据您引用的与最新的 C++1z 工作草案一致的规范,您的第一行代码中的构造不应参与重载解析。

在使用 libc++ 的 clang 中,它无法根据此规范正确编译:

http://melpon.org/wandbox/permlink/7cTyS3luVn1XRXGv

使用最新的 gcc,它会根据此规范错误地编译:

http://melpon.org/wandbox/permlink/CSoB1BTNF3emIuvm

使用最新的 VS-2015,它会根据此规范错误地编译:

http://webcompiler.cloudapp.net

(最后一个链接不包含正确的代码,您必须将其粘贴进去)。

在您的 coliru 链接中,虽然您使用的是 clang,但 clang 隐含地将 gcc 的 libstdc++ 用于 std::lib,因此您的结果与我的一致。

从本次调查来看,似乎只有 libc++ 符合规范和您的期望。但这并不是故事的结局。

您正确引用的最新工作草案规范与 C++14 规范不同,如下所示:

template &lt;class... UTypes&gt; constexpr tuple(tuple&lt;UTypes...&gt;&amp;&amp; u);

需要sizeof...(Types) == sizeof...(Types)。对于所有 iis_constructible<Ti, Ui&&>::valuetrue

效果:对于所有i,初始化*thisith元素为 std::forward&lt;Ui&gt;(get&lt;i&gt;(u)).

备注:此构造函数不应参与重载决议 除非UTypes 中的每种类型都可以隐式转换为 Types中的对应类型。

N4387 改变了规范 C++14 之后。

在 C++14(以及 C++11 中)中,客户端有责任确保所有iis_constructible&lt;Ti, Ui&amp;&amp;&gt;::valuetrue时间>。如果不是,你有未定义的行为。

因此,您的第一行代码在 C++11 和 C++14 中是未定义的行为,并且在 C++1z 工作草案中格式错误。对于未定义的行为,任何事情都可能发生。所以所有的libc++、libstdc++和VS都符合C++11和C++14规范。


更新T.C.下方评论的启发:

请注意,从重载决议中删除这个构造函数可能允许表达式使用另一个构造函数,例如tuple(const tuple&lt;UTypes...&gt;&amp; u)。然而,这个构造函数也被一个类似的 Remarks 子句从重载决议中删除。

std::is_constructible<int&, const int&>::value == false

唉,T.C.可能在工作草案中发现了缺陷。实际规范说:

std::is_constructible<int&, int&>::value == true

因为const 应用于引用,而不是int


似乎 libstdc++ 和 VS 尚未实现 N4387 指定的后 C++14 规范,它几乎指定了您的 cmets 指示应该发生的情况。 libc++ 已经实施N4387 多年,并作为该提案的实施证明。

作业

这需要:

std::is_assignable<int&, const int&>::value == true

这在您的示例作业中实际上是正确的。在这行代码中。但是我认为可以提出一个论点,即它应该要求:

std::is_assignable<int&&, const int&>::value == true

这是错误的。现在,如果我们只进行此更改,您的分配将是未定义的行为,因为规范的这一部分位于 Requires 子句中。因此,如果您真的想做到这一点,您需要将规范移动到一个 Remarks 子句中,其中包含“不参与重载决议”的配方。然后它会像您的 cmets 所指示的那样运行。我会支持这样的提议。但你必须做到,不要问我。但如果您愿意,我会为您提供帮助。

【讨论】:

  • 我实际上不确定第一部分(即 N4387 后)。 tuple(tuple&lt;UTypes...&gt;&amp;&amp; u) 已从重载决议中删除,但在我看来 tuple(const tuple&lt;UTypes...&gt;&amp; u) 是可行的,因为它的 SFINAE 条件 is_constructible&lt;Ti, const Ui&amp;&gt; 在引用折叠时变为 is_constructible&lt;int&amp;, int&amp;&gt;
  • 不,const Ui&amp;Ui == int&amp;&amp;int&amp;const 适用于引用,因此被忽略,而不是引用的类型。
  • @T.C.:啊!您可能在工作草案中发现了缺陷。要做的第一件事是确定预期(或期望)的行为是什么,然后要做的就是弄清楚如何表达。
  • @T.C.您会为const Ui&amp; 问题提交缺陷报告吗?我愿意将其添加到我的待办事项列表中。
  • @xcvr 我认为浅常量是正确的,而不是缺陷;否则你不能从tuple&lt;int&amp;&gt; 左值初始化tuple&lt;int&amp;&gt;。就个人而言,我认为这可能需要一篇论文来正确考虑值/&/&& 的所有各种组合,并确定每个组合的行为方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多