通常,当一个函数返回boost::optional时,我看到很多人返回空大括号{}来指定一个空值,这很好,并且比返回boost::none短。
我试图执行类似的操作,以清空一个boost::optional<int>,但是当调用右边有空大括号的复制赋值操作符(或者很可能是移动赋值op)时,空大括号被转换为int,然后将该值赋值给可选值,因此我最终将变量设置为0,而不是我所期望的空值。下面是一个示例https://godbolt.org/g/HiF92v,如果我使用std::experimental::optional进行同样的尝试,就会得到我所期望的结果(在这个示例中,只需用std::实验性::可选的替换,您就会看到指令变成了mov eax, eax)。
另外,如果我尝试为boost可选类型(一种非整数类型)使用不同的模板参数,那么有些编译器会编译(使用我所期望的行为,这里是http://cpp.sh/5j7n),而其他编译器则不编译。因此,即使对于相同的lib,根据模板arg,行为也是不同的。
我想了解这里发生了什么,我知道这与我使用C++14特性的一个事实有关,这个库没有考虑到这一点。我读了boost/optional头,但我在细节上迷失了方向,我也试图研究编译后的代码,而没有使用类似的结果。
我使用gcc 4.9.2和-std=c++14和boost 1.57。
顺便说一句:我知道我应该使用boost::optional::reset或boost::none,但我试图在其他代码库中保持语义一致。
发布于 2016-11-18 12:30:00
要了解正在发生的事情,请先考虑下面的示例:
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)保持不变,但是第一个重载现在是一个接受任何T的模板。但是,对于enable_if,我们对其进行了限制,因此它只适用于int类型。因此,这是一个相当“人工”的模板,但它做的工作。如果我打电话:
fun(0); // picks fun(T)选择人工模板。但如果我打字:
fun({}); // picks fun(Wrap)人工模板仍然是模板,因此在这种情况下从不考虑类型推断,唯一可见的重载是fun(Wrap),因此它被选中。
在std::optional<T>中也采用了同样的技巧:它没有来自T的赋值。相反,它有一个类似的人工赋值模板,它接受任何U,但后来受到限制,因此T == U。您可以在参考实现这里中看到它。
boost::optional<T>在C++11之前就已经实现了,不知道这个“重置成语”。因此,它具有来自T的正常赋值,如果T恰好是标量,则首选来自T的赋值。这就是区别所在。
考虑到所有这些,我认为Boost.Optional有一个错误,它所做的事情与std::optional相反。即使它不能在Boost.Optional中实现,它至少也不应该编译,以避免运行时的意外。
https://stackoverflow.com/questions/40391244
复制相似问题