似乎包含一个或多个引用的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&
将折叠为左值ref):
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
虽然移动(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 效果:对于所有
i
,构造函数使用i
初始化*this
的{{1}}元素。20 备注:除非
std::forward<Ui>(get<i>(u))
对所有is_constructible<Ti, Ui&&>::value
都为真,否则此构造函数不应参与重载决策。当且仅当i
对于至少一个explicit
为假时,构造函数为is_convertible<Ui&&, Ti>::value
。
这些不是唯一受影响的小节。
问题是,为什么需要这种行为?此外,如果标准的其他部分正在发挥作用,或者我误解了它,请解释我出错的地方。
谢谢!
答案 0 :(得分:7)
构造函数
让我们从构造函数(第一行)开始:
我们都知道(只是澄清),decltype(std::forward_as_tuple(9))
是std::tuple<int&&>
。
另请注意:
std::is_constructible<int&, int&&>::value == false
在所有编译器平台上。因此,根据您引用的规范,这与最新的C ++ 1z工作草案一致,您的第一行代码中的构造不应参与重载决策。
在使用libc ++的过程中,根据此规范,它无法正确编译:
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)
。对于所有 i ,is_constructible<Ti, Ui&&>::value
为true
。效果:对于所有 i ,初始化
*this
的 ith 元素std::forward<Ui>(get<i>(u))
。备注:此构造函数不应参与重载解析 除非
UTypes
中的每个类型都可以隐式转换为它Types
中的相应类型。
在 C ++ 14之后,N4387改变了规范。
在C ++ 14中(以及在C ++ 11中),onus位于客户端上,以确保所有is_constructible<Ti, Ui&&>::value为true
> I 的。如果不是,那么你有不明确的行为。
因此,您的第一行代码是C ++ 11和C ++ 14中未定义的行为,而C ++ 1z工作草案中则不正确。对于未定义的行为,任何事情都可能发生因此,所有libc ++,libstdc ++和VS都符合C ++ 11和C ++ 14规范。
更新受以下T.C.评论的启发:
请注意,从重载决策中删除此一个构造函数可能允许表达式使用另一个构造函数,例如tuple(const tuple<UTypes...>& u)
。但是,此类构造函数也会通过类似的备注子句从重载解析中删除。
std::is_constructible<int&, const int&>::value == false
唉,T.C。可能在工作草案中发现了一个缺陷。实际规格说:
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 子句中。因此,如果您确实想要这样做,则需要将规范移动到备注子句中,其子句为“不参与重载解析”。然后它会表现为您的评论表明您期望它。我会支持这样的提议。但你必须做到,不要问我。但如果你愿意,我会帮你做这件事。