boost optional 和 std::experimental optional 分配之间的区别
Difference between boost optional and std::experimental optional assignment
通常当一个函数 returns boost::optional
我看到很多人返回一个空括号 {}
来指定一个空值,这工作正常并且比返回更短boost::none
.
我尝试做一些类似于空 a boost::optional<int>
的事情,但是当调用复制赋值运算符(或者很可能是移动赋值运算符)时,右侧有一个空大括号,空大括号被转换到一个 int 然后将该值分配给可选,所以我最终将变量设置为 0 而不是我期望的空值。这是一个示例 https://godbolt.org/g/HiF92v,如果我对 std::experimental::optional
进行相同的尝试,我会得到我期望的结果(只需在示例中替换为 std::experimental::optional,您将看到指令变为 mov eax, eax
).
此外,如果我尝试为 boost 可选(非整数类型)使用不同的模板参数,一些编译器会编译(具有我期望的行为,这里有一个示例 http://cpp.sh/5j7n),而其他编译器则不会.因此,即使对于相同的库,行为也会根据模板参数而有所不同。
我想了解这里发生了什么,我知道这与我在库中使用 C++14 功能这一事实有关,但在设计中并未考虑到这一点.我阅读了 boost/optional
header 但我迷失了细节,我也尝试研究编译后的代码而不内联类似的结果。
我正在使用带有 -std=c++14 和 boost 1.57 的 gcc 4.9.2。
顺便说一句:我知道我应该使用 boost::optional::reset
或 boost::none
,但我试图与代码库其余部分的语义保持一致。
要了解发生了什么,请先考虑这个例子:
void fun(int) { puts("f int"); }
void fun(double) { puts("f double"); }
int main() {
fun({}); // error
}
这会导致编译器错误,因为重载解析是不确定的:double
和 int
同样适合。但是,如果非标量类型起作用,情况就不同了:
struct Wrap{};
void fun(int) { puts("f(int)"); }
void fun(Wrap) { puts("f(Wrap)"); }
int main() {
fun({}); // ok: f(int) selected
}
这是因为标量更匹配。如果出于某种原因,我想要相同的两个重载,但同时我想要 fun({})
到 select 重载 fun(Wrap)
,我可以稍微调整一下定义:
template <typename T>
std::enable_if_t<std::is_same<int, std::decay_t<T>>::value>
fun(T) { puts("f int"); }
void fun(Wrap) { puts("f(Wrap)"); }
也就是说,fun(Wrap)
保持不变,但第一个重载现在是一个采用任何 T
的模板。但是我们用 enable_if
限制了它,所以它只适用于 int
类型。所以,这是一个相当不错的 'artificial' 模板,但它完成了工作。如果我打电话:
fun(0); // picks fun(T)
人工模板得到 selected。但是如果我输入:
fun({}); // picks fun(Wrap)
人工模板仍然是模板,所以在这种情况下从不考虑类型推导,唯一可见的重载是fun(Wrap)
,所以它被selected.
std::optional<T>
中使用了相同的技巧:它没有来自 T
的赋值。相反,它有一个类似的人工分配模板,它接受任何 U
,但后来受到约束,因此 T == U
。您可以在参考实现中看到它 here.
boost::optional<T>
在C++11之前已经实现,不知道这个'reset idiom'。因此,它具有来自 T
的正常赋值,并且在 T
恰好是标量的情况下,首选来自 T
的赋值。因此差异。
鉴于所有这些,我认为 Boost.Optional 有一个错误,它做了与 std::optional
相反的事情。即使它不能在 Boost.Optional 中实现,它至少应该编译失败,以避免 运行 时间意外。
通常当一个函数 returns boost::optional
我看到很多人返回一个空括号 {}
来指定一个空值,这工作正常并且比返回更短boost::none
.
我尝试做一些类似于空 a boost::optional<int>
的事情,但是当调用复制赋值运算符(或者很可能是移动赋值运算符)时,右侧有一个空大括号,空大括号被转换到一个 int 然后将该值分配给可选,所以我最终将变量设置为 0 而不是我期望的空值。这是一个示例 https://godbolt.org/g/HiF92v,如果我对 std::experimental::optional
进行相同的尝试,我会得到我期望的结果(只需在示例中替换为 std::experimental::optional,您将看到指令变为 mov eax, eax
).
此外,如果我尝试为 boost 可选(非整数类型)使用不同的模板参数,一些编译器会编译(具有我期望的行为,这里有一个示例 http://cpp.sh/5j7n),而其他编译器则不会.因此,即使对于相同的库,行为也会根据模板参数而有所不同。
我想了解这里发生了什么,我知道这与我在库中使用 C++14 功能这一事实有关,但在设计中并未考虑到这一点.我阅读了 boost/optional
header 但我迷失了细节,我也尝试研究编译后的代码而不内联类似的结果。
我正在使用带有 -std=c++14 和 boost 1.57 的 gcc 4.9.2。
顺便说一句:我知道我应该使用 boost::optional::reset
或 boost::none
,但我试图与代码库其余部分的语义保持一致。
要了解发生了什么,请先考虑这个例子:
void fun(int) { puts("f int"); }
void fun(double) { puts("f double"); }
int main() {
fun({}); // error
}
这会导致编译器错误,因为重载解析是不确定的:double
和 int
同样适合。但是,如果非标量类型起作用,情况就不同了:
struct Wrap{};
void fun(int) { puts("f(int)"); }
void fun(Wrap) { puts("f(Wrap)"); }
int main() {
fun({}); // ok: f(int) selected
}
这是因为标量更匹配。如果出于某种原因,我想要相同的两个重载,但同时我想要 fun({})
到 select 重载 fun(Wrap)
,我可以稍微调整一下定义:
template <typename T>
std::enable_if_t<std::is_same<int, std::decay_t<T>>::value>
fun(T) { puts("f int"); }
void fun(Wrap) { puts("f(Wrap)"); }
也就是说,fun(Wrap)
保持不变,但第一个重载现在是一个采用任何 T
的模板。但是我们用 enable_if
限制了它,所以它只适用于 int
类型。所以,这是一个相当不错的 'artificial' 模板,但它完成了工作。如果我打电话:
fun(0); // picks fun(T)
人工模板得到 selected。但是如果我输入:
fun({}); // picks fun(Wrap)
人工模板仍然是模板,所以在这种情况下从不考虑类型推导,唯一可见的重载是fun(Wrap)
,所以它被selected.
std::optional<T>
中使用了相同的技巧:它没有来自 T
的赋值。相反,它有一个类似的人工分配模板,它接受任何 U
,但后来受到约束,因此 T == U
。您可以在参考实现中看到它 here.
boost::optional<T>
在C++11之前已经实现,不知道这个'reset idiom'。因此,它具有来自 T
的正常赋值,并且在 T
恰好是标量的情况下,首选来自 T
的赋值。因此差异。
鉴于所有这些,我认为 Boost.Optional 有一个错误,它做了与 std::optional
相反的事情。即使它不能在 Boost.Optional 中实现,它至少应该编译失败,以避免 运行 时间意外。