为什么 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,因为在这种情况下它的行为似乎是错误的。
为什么输出2
?我希望1
.std::optional
的构造函数似乎更喜欢向内部类型(int
)进行强制转换,尽管事实上可以使用外部类型(std::optional<int>
)。根据C++标准,这是正确的吗?如果是这样,是否有理由标准没有规定更喜欢尝试转换为外部类型?我会发现这更合理,并且可以想象它使用enable_if
和is_convertible
来实现,如果可以转换为外部类型,则可以禁用 ctor。否则,如果用户类中要std::optional<T>
的每个强制转换运算符 - 即使它是完全匹配的 - 如果还有一个要T
,原则上也会被忽略。我会觉得这很令人讨厌。
我昨天发布了一个类似的问题,但可能没有准确说明我的问题,因为由此产生的讨论更多是关于 GCC 错误。这就是为什么我在这里再次更明确地问。
如果巴里的出色答案仍然不清楚,这是我的版本,希望对您有所帮助。
最大的问题是为什么在直接初始化中不首选用户定义的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>
",但它实际上意味着">从my_foo
创建一个临时std::optional<int>
并从中移动分配",如[expr.static.cast]/4中所述:
如果
T
是引用类型,则效果与执行 声明和初始化T t(e);
对于一些发明的临时 变量t
([dcl.init]),然后使用临时变量作为 转换的结果。否则,结果对象为 从e
直接初始化。
所以它变成了:
my_opt = std::optional<int>(my_foo);
我们又回到了以前的情况;my_opt
随后从一个临时optional
初始化,已经持有一个2
。
转发引用上的重载问题是众所周知的。斯科特·迈尔斯(Scott Myers)在他的著作《有效的现代C++》第26章中广泛讨论了为什么在"通用参考"上超载是一个坏主意。这样的模板将不知疲倦地消除您扔给它们的任何类型的内容,这将使所有不完全匹配的东西黯然失色。所以我很惊讶委员会选择了这条路线。
至于为什么会这样,在提案N3793和标准中直到2016年11月15日确实是
optional(const T& v);
optional(T&& v);
但后来作为 LWG 缺陷 2451 的一部分,它被更改为
template <class U = T> optional(U&& v);
理由如下:
如下代码目前格式不正确(感谢 STL 令人信服的例子):
optional<string> opt_str = "meow";
这是因为它需要两个用户定义的转换(从
const char*
到string
,以及从string
到optional<string>
),其中 语言只允许一种。这可能是一个惊喜和一个 给用户带来不便。
optional<T>
应该可以从任何U
隐式转换 隐式转换为T
。这可以实现为非显式 构造函数模板optional(U&&)
,仅通过 SFINAE 启用 如果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&
初始化需要的步骤更少:
optional<T>::optional(const T& t) {
value = t;
has_value = true;
}
如果存在从表达式到所需类型的隐式转换序列,并且生成的对象是从表达式直接初始化的,则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()
.
- 有没有一种方法可以通过"typedef"为重新定义的基本类型定义特征和强制转换运算符
- 构造函数和转换运算符之间的重载解析
- 分配给转换运算符失败-C++
- 转换运算符不适用于sleep_until
- 继承模板化转换运算符
- 模板转换运算符在 clang 6 和 clang 7 之间的区别
- 如何在模板化转换运算符中消除此构造的歧义?
- 为什么选择转换运算符的重载?
- 如何避免强制转换运算符 () 和访问运算符 [] 冲突?
- 如果可能的话,C++总是更喜欢右值引用转换运算符而不是常量左值引用吗?
- 了解转换运算符的选择C++
- 多个隐式转换运算符
- 这个typedef和转换运算符语法是什么意思
- 为什么转换运算符调用复制构造函数两次,而等效函数只调用它一次
- 类模板忽略了用户定义的转换运算符(非模板不忽略)
- 为什么在std::for_each()返回时调用转换运算符
- 为什么 std::optional 的强制转换运算符被忽略了
- 使用用户定义的转换运算符推导函数模板参数
- 模板转换运算符的分辨率不明确
- 统一初始化是隐式发生的,即使 int 强制转换运算符是使用 explicit 关键字声明的.原因是什么?