13

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

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

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

template <class... UTypes> tuple& operator=(const tuple<UTypes...>& u);

9要求: sizeof...(Types) == sizeof...(UTypes)并且is_assignable<Ti&, const Ui&>::value适用于所有i.

10效果:将 u 的每个元素分配给 *this 的对应元素。

11返回: *this

尽管 move(ish) 构造部分20.4.2.1.20不太清楚(is_constructible<int&, int&&>返回false):

template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);

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

19效果:对于 all ,构造函数用 withi初始化第ith 个元素*thisstd::forward<Ui>(get<i>(u))

20备注:此构造函数不应参与重载决议,除非is_constructible<Ti, Ui&&>::value对 all 为真i。构造函数是explicit当且仅当is_convertible<Ui&&, Ti>::value对于至少一个为假i

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

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

谢谢!

4

1 回答 1

7

构造函数

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

正如我们都知道(只是澄清),decltype(std::forward_as_tuple(9))std::tuple<int&&>.

另请注意:

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 <class... UTypes> constexpr tuple(tuple<UTypes...>&& u);

要求sizeof...(Types) == sizeof...(Types)is_constructible<Ti, Ui&&>::value适用true于所有

效果:对于所有i,初始化with 的第 i 个元素。*thisstd::forward<Ui>(get<i>(u))

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

在C++14之后更改规范的是N4387 。

在 C++14(以及 C++11 中)中,客户端有责任确保这is_constructible<Ti, Ui&&>::value适用true于所有i。如果不是,你有未定义的行为。

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


受以下TC评论启发的更新:

请注意,从重载决议中删除这个构造函数可能允许表达式使用另一个构造函数,例如tuple(const tuple<UTypes...>& u). 但是,此构造函数也通过类似的Remarks子句从重载决议中删除。

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

唉,TC 可能在工作草案中发现了一个缺陷。实际规格说:

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

因为const应用于参考,而不是int.


似乎 libstdc++ 和 VS 尚未实现N4387指定的后 C++14 规范,并且几乎指定了您的评论表明应该发生什么。libc++ 已经实施N4387多年,并作为该提案的实施证明。

那作业

这需要:

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

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

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

这是错误的。现在,如果我们只进行此更改,则您的分配将是未定义的行为,因为规范的这一部分位于Requires子句中。因此,如果您真的想正确执行此操作,则需要将规范移动到带有“不参与重载解决方案”的配方的Remarks子句中。然后它会按照您的评论表明您期望的那样运行。我会支持这样的提议。但你必须做到,不要问我。但是,如果您愿意,我会为您提供帮助。

于 2016-01-02T18:42:42.800 回答