boost可选和std :: experimental可选赋值之间的区别

时间:2016-11-02 23:04:19

标签: c++ boost c++14 gcc4.9 boost-optional

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

我尝试做类似于清空boost::optional<int>的事情,但是当在右侧调用带有空括号的复制赋值运算符(或者很可能是移动赋值op)时,空括号将转换为int然后将该值赋给可选项,因此我最终将变量设置为0而不是我期望的空值。这是一个示例https://godbolt.org/g/HiF92v,如果我尝试使用std::experimental::optional,我会得到我期望的结果(在示例中只需用std :: experimental :: optional替换,你会看到指令变为mov eax, eax)。

此外,如果我尝试使用不同的模板参数进行boost可选(非整数类型),一些编译器会编译(使用我期望的行为,这里的示例为http://cpp.sh/5j7n)而其他编译器则没有。因此,即使对于相同的lib,根据模板arg,行为也是不同的。

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

我正在使用gcc 4.9.2和-std = c ++ 14并提升1.57。

btw:我知道我应该使用boost::optional::resetboost::none,但我试图与其余代码库中的语义保持一致。

1 个答案:

答案 0 :(得分:10)

要了解正在发生的事情,请先考虑以下示例:

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({})选择重载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类型。所以,这是一个非常“人为”的模板,但它确实起到了作用。如果我打电话:

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

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

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

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

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

boost::optional<T>已经在C ++ 11之前实现,没有意识到这个'重置习惯用法'。因此,它具有来自T的正常分配,并且在T碰巧是标量的情况下,来自T的此分配是首选。因此存在差异。

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