此代码
#include <iostream>
#include <optional>
struct foo
{
explicit operator std::optional<int>() {
return std::optional<int>( 1 );
}
explicit operator int() {
return 2;
}
};
int main()
{
foo my_foo;
std::optional<int> my_opt( my_foo );
std::cout << "constructor: " << my_opt.value() << std::endl;
my_opt = static_cast<std::optional<int>>(my_foo);
std::cout << "static_cast: " << my_opt.value() << std::endl;
}
constructor: 2
static_cast: 2
在Clang 4.0.0和MSVC 2017(15.3)中。 (我们暂时忽略GCC,因为在这种情况下它的行为似乎是buggy。)
为什么输出2
?我希望1
。 std::optional
的构造函数似乎更倾向于转换为内部类型(int
),尽管可以使用外部类型(std::optional<int>
)的强制转换。根据C ++标准,这是正确的吗?如果是这样,那么标准是否有理由不希望尝试转换为外部类型?我会发现这更合理,并且可以想象如果可以转换为外部类型,则使用enable_if
和is_convertible
来禁用ctor。否则,如果还有一个std::optional<T>
,则原则上将忽略用户类中T
的每个强制转换操作符 - 即使它是完美匹配 - 。我觉得这很讨厌。
我昨天发布了一些similar question,但可能没有准确地说明我的问题,因为最终的讨论更多是关于GCC的错误。这就是我在这里再次明确要求的原因。
答案 0 :(得分:4)
如果表达式中有implicit conversion sequence到所需类型,并且结果对象从表达式中direct-initialized,则static_cast
有效。所以写道:
my_opt = static_cast<std::optional<int>>(my_foo);
执行与执行相同的步骤:
std::optional<int> __tmp(my_foo); // direct-initialize the resulting
// object from the expression
my_opt = std::move(__tmp); // the result of the cast is a prvalue, so move
一旦我们开始构建,我们将遵循与previous answer相同的步骤,枚举构造函数,最终选择使用operator int()
的构造函数模板。
答案 1 :(得分:4)
如果Barry的优秀答案仍然不明确,这是我的版本,希望它有所帮助。
最大的问题是为什么在直接初始化中用户定义的转换不是optional<int>
首选:
std::optional<int> my_opt(my_foo);
毕竟,有一个构造函数optional<int>(optional<int>&&)
和用户定义的my_foo
到optional<int>
的转换。
原因是template<typename U> optional(U&&)
构造函数模板,当T
(int
)可以从U
构造而U
不是{时,它应该激活{1}}也不std::in_place_t
,并从中直接初始化optional<T>
。它确实如此,剔除了T
。
最终生成的optional(foo&)
类似于:
optional<int>
class optional<int> {
. . .
int value_;
. . .
optional(optional&& rhs);
optional(foo& rhs) : value_(rhs) {}
. . .
需要用户定义的转化,而optional(optional&&)
则与optional(foo&)
完全匹配。因此它获胜,并从my_foo
直接初始化int
。仅在此时选择my_foo
作为初始化operator int()
的更好匹配。结果因此变为int
。
2)如果是2
,虽然听起来就像“初始化my_opt = static_cast<std::optional<int>>(my_foo)
一样 - 如果它是my_opt
” ,它实际上 表示 “从std::optional<int>
创建一个临时std::optional<int>
并从该中移动 - 分配” [expr.static.cast]/4:
如果
my_foo
是引用类型,则效果与执行相同 声明和初始化
T
对于一些发明的临时 变量T t(e);
([dcl.init])然后使用临时变量作为 转换的结果。否则,结果对象是 从t
直接初始化。
所以它变成了:
e
我们又回到了以前的状况; my_opt = std::optional<int>(my_foo);
随后从暂时my_opt
初始化,已经持有optional
。
转发引用上的重载问题是众所周知的。斯科特迈尔斯在第26章的 Effective Modern C ++ 一书中广泛讨论了为什么在“通用引用”上超载是一个坏主意。这些模板将不知疲倦地消除你扔给他们的任何类型,这将掩盖一切和任何不完全匹配的东西。所以我很惊讶委员会选择了这条路线。
至于原因的原因,在提案N3793和标准中直到Nov 15, 2016确实
2
然后作为LWG defect 2451的一部分,它变成了
optional(const T& v);
optional(T&& v);
具有以下基本原理:
以下代码目前格式不正确(感谢STL for 引人注目的例子):
template <class U = T> optional(U&& v);
这是因为它需要两个用户定义的转换(来自
optional<string> opt_str = "meow";
到const char*
,从string
到string
) 语言只允许一个。这可能是一个惊喜和一个 给用户带来不便。
optional<string>
应该可以从任何optional<T>
隐式转换 隐式可转换为U
。这可以作为非显式实现 构造函数模板T
,仅通过SFINAE启用 如果optional(U&&)
和is_convertible_v<U, T>
,加上任何 避免与其他人产生歧义所需的其他条件 构造...
最后我认为is_constructible_v<T, U>
排名高于T
是可以的,毕竟在可能具有价值和之间的选择是相当不寻常的选择> 值。
在性能方面,从optional<T>
初始化而不是从另一个T
初始化也是有益的。 optional<T>
通常实现为:
optional
所以从template<typename T>
struct optional {
union
{
char dummy;
T value;
};
bool has_value;
};
初始化它看起来像
optional<T>&
而从optional<T>::optional(const optional<T>& rhs) {
has_value = rhs.has_value;
if (has_value) {
value = rhs.value;
}
}
初始化需要更少的步骤:
T&