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::resetboost::none,但我试图与代码库其余部分的语义保持一致。

要了解发生了什么,请先考虑这个例子:

void fun(int) { puts("f int"); }
void fun(double) { puts("f double"); }

int main() {
  fun({}); // error
}

这会导致编译器错误,因为重载解析是不确定的:doubleint 同样适​​合。但是,如果非标量类型起作用,情况就不同了:

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 中实现,它至少应该编译失败,以避免 运行 时间意外。