为什么我不能使用 optional<S>(其中 S != T)构造 optional<T>?

9
如果我们拥有
 std::experimental::optional<int> x;

以下两行都无法编译:

 std::experimental::optional<unsigned int> y; y = x;
 std::experimental::optional<unsigned int> z(x);

尽管对我来说,这似乎就像将int赋值给unsigned int一样有道理。为什么这样不起作用呢?也就是说,库不实现这些情况下的复制构造函数和赋值操作符所避免的陷阱是什么?


@DavidPacker:我不明白你在说什么...你提供的链接是当前可用的赋值运算符,其中并不包括其他可选类型的运算符。你能解释一下这与移动赋值有什么特别关系吗? - einpoklum
抱歉,我没有检查第四个移动赋值运算符的详细信息。似乎即使那个也不允许不同类型的可选项进行赋值。真的没有任何文档可以证明原因,唯一能给出答案的可能是创建该结构的团队成员。 - Andy
说实话,这是因为C++语言很棒,但其标准库不够好。自从我明白了这一点,作为C++开发人员的生活变得更加简单和美好。你在抱怨optional吗?我首先会抱怨IOStream库、字符串库以及缺乏好的、开箱即用的、自上而下的标准库(2016年)。好的一面是,在C++中,你可以自己编写任何东西,而不必等待新标准的发布。顺便说一句,你总是可以转向boost-optional或Facebook Folly的optional。 - David Haim
@DavidHaim:我觉得我不应该在不理解为什么首先缺少构造函数和操作符“=”的情况下,就开始向std命名空间构造添加它们。 - einpoklum
@einpoklum 你的问题是合理的。但我的观点是,我认为答案不会比“这就是决定的方式”更好,就像标准库中的许多其他问题一样。 - David Haim
显示剩余4条评论
3个回答

4
您无法这样做,因为std::experimental::optional没有适当的构造函数和赋值运算符重载。
提供这样的接口肯定是可能的。实际上,基于建议接口的Boost Optional确实有一个从另一个optional实例构造的重载构造函数和赋值运算符。 建议有一个“与Boost.Optional比较”的部分,其中包含了讨论过的重载表格。该提案并没有为这些差异给出理由,除了一般性的声明:

当前提案反映了我们在界面的明确性、通用性和灵活性之间的任意选择。

是否包括这些重载的决定的基本原理在git repo中进行了讨论,该repo具有参考实现,并在ISO C++标准-未来提案邮件列表中进行了讨论。
在Github问题中,提案作者Andrzej Krzemieński展示了这种可能导致歧义的重载问题:

It is just not clear what should happen in the following situation:

struct Tool
{
  Tool(int); // ctor 1
  Tool(optional<int>); // ctor 2
};

optional<int> oi = 1;
optional<Tool> ot = oi;

Which constructor of Tool should be called and why?


йӮЈдёӘжғ…еўғдёӢеә”иҜҘеҸ‘з”ҹд»Җд№ҲдёҚжё…жҘҡеҗ—пјҹе•ҠпјҢжҳҜиҰҒд»ҺеҸҜйҖүзҡ„intжһ„йҖ еҶ…йғЁзҡ„ToolпјҢиҝҳжҳҜиҰҒд»ҺеҸҜйҖүзҡ„intжһ„йҖ optional<Tool>гҖӮ - Yakk - Adam Nevraumont
@Yakk 我会认为这就是歧义的本质。 - eerorika
@Yakk:完全同意您的观点。我认为当optional<int>被启用时,两个构造函数必须完全相同,以便消除歧义。唉,如果我有时间进行标准游说就好了... - einpoklum
@einpoklum,这两个构造函数的行为方式不是“可选项”的业务。 “可选项”的业务是选择哪一个进行调用。我认为你可以借鉴单子的一些思路并使用“int”构造函数,但我也能理解相反的论点。 - Yakk - Adam Nevraumont
@Yakk:无论他们选择怎么称呼都可以,因为如果不符合预期,那就是“工具的问题”。编写工具的人有责任编写代码,使其不受影响。虽然我听说过单子(monads)的概念,但我认为拥有一个接受可选项的构造函数就像你拥有一个接受整数和一个接受nullopt的构造函数一样,即类似于Scala中的模式匹配。 - einpoklum
显示剩余2条评论

3
以下是关于将构造函数转换的基本论点(作者名称不清楚,但来自此线程):
我们有两个构造函数: 1. unboxing: 从`optional`构造`optional`,如果已初始化,则从`U`构造`T` 2. forwarding: 从`U`构造`optional`
问题在于,如果`T`既可以从`U`构造,也可以从`optional`构造,我们就会出现歧义,这时我们可以选择以下三种方法之一: 1. 编译错误并让程序员决定 2. 优先选择unboxing构造函数 3. 优先选择forwarding构造函数
我认为这不是什么大问题,但我不想在这里开始这个讨论。

3
作为该提案的共同作者,我可以确认这是该功能最大的争议点。你们没有在TS(或标准,在那个阶段)中看到它的原因更多是政治上的而不是技术上的。具有争议性的提案往往会被拒绝,所以最好不提出有争议的部分。理由是,没有这种转换的std::optional比没有std::optional更好。 - Andrzej
@Andrzej:你是否有你曾经考虑过但最终放弃的转换代码的链接? - einpoklum
没有任何问题。我无法决定角落案例的语义,因此我从未达到需要标准化的阶段。上面的链接(https://groups.google.com/a/isocpp.org/d/topic/std-proposals/RTUK2xO0tzs/discussion)包含讨论的变体和一个变体的签名。如果我现在要添加此转换,我将执行std::tuple的操作。 - Andrzej
@Andrzej:你的意思是std::tuple在使用与元组元素类型不同的单个元素进行构造时会发生什么? - einpoklum
是的。也就是说,(1)优先选择拆箱操作,(2)有条件地显式操作。 - Andrzej

2
将英文翻译成中文:

n3672提案与Boost.Optional进行比较。其中一个关键区别是,std::optional不允许从optional<U>转换为optional<T>std::optional在很大程度上是基于Boost.Optional建模的(至少在1.48.0版本中,不确定自那以后有什么改变)。

ISO C++标准-未来提案邮件列表上的讨论讨论了他们为什么删除了这个功能。警告:长文


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接