10

通常当一个函数返回时,boost::optional我看到很多人返回一个空括号{}来指定一个空值,它工作正常并且比返回短boost::none

我试图做一些类似于 empty 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的示例)而其他编译器则不会。因此,即使对于相同的库,行为也会根据模板 arg 有所不同。

我想了解这里发生了什么,我知道这与我将 C++14 功能用于设计中没有考虑到这一点的库这一事实有关。我阅读了boost/optional标题,但我迷失了细节,我还尝试研究编译后的代码,而不用内联类似的结果。

我正在使用带有 -std=c++14 和 boost 1.57 的 gcc 4.9.2。

顺便说一句:我知道我应该使用boost::optional::resetor boost::none,但我试图与代码库其余部分的语义保持一致。

4

1 回答 1

12

要了解发生了什么,请首先考虑以下示例:

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({})选择重载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)保持不变,但第一个重载现在是一个接受 any 的模板T。但是enable_if我们对它进行了约束,使其仅适用于 type int。所以,这是一个相当“人工”的​​模板,但它确实可以完成工作。如果我打电话:

fun(0); // picks fun(T)

人工模板被选中。但如果我输入:

fun({}); // picks fun(Wrap)

人工模板仍然是模板,因此在这种情况下,在类型推导中从不考虑它,唯一可见的重载是fun(Wrap),因此它被选中。

中采用了相同的技巧std::optional<T>:它没有来自 的赋值T。相反,它有一个类似的人工分配模板,它接受 any U,但后来受到约束,因此T == U. 您可以在此处的参考实现中看到它。

boost::optional<T>已经在 C++11 之前实现,不知道这个'reset idiom'。因此它有一个来自 的正常赋值T,并且在T碰巧是一个标量的情况下,这个赋值T是首选的。因此差异。

鉴于这一切,我认为 Boost.Optional 有一个错误,它的作用与std::optional. 即使它不能在 Boost.Optional 中实现,它至少应该无法编译,以避免运行时意外。

于 2016-11-18T12:30:00.167 回答