为什么 std::optional 的转换运算符会被忽略?
Why is a cast operator to std::optional ignored?
这个代码
#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 来实现它。否则,用户 class 中每个转换为 std::optional<T>
的运算符 - 即使它是完美匹配 - 如果还有一个转换为 T
,原则上也会被忽略。我会觉得这很讨厌。
我昨天发布了一些 但可能没有准确说明我的问题,因为由此产生的讨论更多是关于 GCC 错误。这就是为什么我在这里更明确地再次询问。
如果表达式中有 implicit conversion sequence from the expression to the desired type, and the resulting object is direct-initialized,A 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
一旦开始构建,我们将按照与我的 相同的步骤,枚举构造函数,最终选择使用 operator int()
.
的构造函数模板
如果 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
是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
的精确匹配。所以它赢了,并直接从 my_foo
初始化 int
。只有在此时才选择 operator int()
作为更好的匹配来初始化 int
。结果因此变成 2
.
2) 在 my_opt = static_cast<std::optional<int>>(my_foo)
的情况下,尽管它 听起来 像“ 初始化 my_opt
就好像它是 std::optional<int>
",它实际上 的意思是 "从 [=21 创建一个临时的 std::optional<int>
=] 并从 " 移动赋值,如 [expr.static.cast]/4:
中所述
If T
is a reference type, the effect is the same as performing the
declaration and initialization
T t(e);
for some invented temporary
variable t
([dcl.init]) and then using the temporary variable as the
result of the conversion. Otherwise, the result object is
direct-initialized from e
.
所以变成:
my_opt = std::optional<int>(my_foo);
我们又回到了以前的状态; my_opt
随后从临时 optional
初始化,已经持有 2
.
转发引用的超载问题是众所周知的。 Scott Myers 在他的书 Effective Modern C++ 的第 26 章中广泛讨论了为什么在 "universal references" 上重载是个坏主意。这样的模板会不知疲倦地消除你扔给它们的任何类型,这将掩盖一切和任何不完全匹配的东西。所以我很惊讶委员会选择了这条路线。
至于为什么会这样,提案N3793 and in the standard until Nov 15, 2016中确实是
optional(const T& v);
optional(T&& v);
但后来作为 LWG defect 2451 的一部分,它变成了
template <class U = T> optional(U&& v);
理由如下:
Code such as the following is currently ill-formed (thanks to STL for
the compelling example):
optional<string> opt_str = "meow";
This is because it would require two user-defined conversions (from
const char*
to string
, and from string
to optional<string>
) where the
language permits only one. This is likely to be a surprise and an
inconvenience for users.
optional<T>
should be implicitly convertible from any U
that is
implicitly convertible to T
. This can be implemented as a non-explicit
constructor template optional(U&&)
, which is enabled via SFINAE only
if is_convertible_v<U, T>
and is_constructible_v<T, U>
, plus any
additional conditions needed to avoid ambiguity with other
constructors...
最后我觉得 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&
初始化需要的步骤更少:
optional<T>::optional(const T& t) {
value = t;
has_value = true;
}
这个代码
#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 来实现它。否则,用户 class 中每个转换为 std::optional<T>
的运算符 - 即使它是完美匹配 - 如果还有一个转换为 T
,原则上也会被忽略。我会觉得这很讨厌。
我昨天发布了一些
A 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
一旦开始构建,我们将按照与我的 operator int()
.
如果 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
是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
的精确匹配。所以它赢了,并直接从 my_foo
初始化 int
。只有在此时才选择 operator int()
作为更好的匹配来初始化 int
。结果因此变成 2
.
2) 在 my_opt = static_cast<std::optional<int>>(my_foo)
的情况下,尽管它 听起来 像“ 初始化 my_opt
就好像它是 std::optional<int>
",它实际上 的意思是 "从 [=21 创建一个临时的 std::optional<int>
=] 并从 " 移动赋值,如 [expr.static.cast]/4:
If
T
is a reference type, the effect is the same as performing the declaration and initializationT t(e);
for some invented temporary variablet
([dcl.init]) and then using the temporary variable as the result of the conversion. Otherwise, the result object is direct-initialized frome
.
所以变成:
my_opt = std::optional<int>(my_foo);
我们又回到了以前的状态; my_opt
随后从临时 optional
初始化,已经持有 2
.
转发引用的超载问题是众所周知的。 Scott Myers 在他的书 Effective Modern C++ 的第 26 章中广泛讨论了为什么在 "universal references" 上重载是个坏主意。这样的模板会不知疲倦地消除你扔给它们的任何类型,这将掩盖一切和任何不完全匹配的东西。所以我很惊讶委员会选择了这条路线。
至于为什么会这样,提案N3793 and in the standard until Nov 15, 2016中确实是
optional(const T& v);
optional(T&& v);
但后来作为 LWG defect 2451 的一部分,它变成了
template <class U = T> optional(U&& v);
理由如下:
Code such as the following is currently ill-formed (thanks to STL for the compelling example):
optional<string> opt_str = "meow";
This is because it would require two user-defined conversions (from
const char*
tostring
, and fromstring
tooptional<string>
) where the language permits only one. This is likely to be a surprise and an inconvenience for users.
optional<T>
should be implicitly convertible from anyU
that is implicitly convertible toT
. This can be implemented as a non-explicit constructor templateoptional(U&&)
, which is enabled via SFINAE only ifis_convertible_v<U, T>
andis_constructible_v<T, U>
, plus any additional conditions needed to avoid ambiguity with other constructors...
最后我觉得 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&
初始化需要的步骤更少:
optional<T>::optional(const T& t) {
value = t;
has_value = true;
}