My colleague told me that boost::optional suffers from missing the ability of building an optional of a member from an optional of its parent object.
Let’s consider the following code:
1 2 3 4 5 6 |
struct Foo { int bar; }; boost::optional<Foo> foo = getFoo(); boost::optional<int> bar = foo ? foo->bar : boost::optional<int>{}; |
The intent is to get an uninitialized optional of the member type (int) when the optional of the its parent (Foo) is uninitialized and an initialized optional when its parent is initialized. That works, but remains repetitive. We could reduce like this:
1 |
auto bar = foo ? foo->bar : boost::optional<int>{}; |
But this code still repeats the type (int) of the member we want to capture and requires some code maintenance if one changes its type. One way to overcome this would be:
1 |
auto bar = foo ? foo->bar : boost::optional<decltype(foo->bar)>{}; |
But there are still repetitions and remains ugly for the programmer’s eye.
At the end, one would like to write something like this:
1 |
auto bar = boost::make_optional(foo, foo->bar); |
But the evaluation of foo->bar will end up with an undefined behaviour when foo is uninitialized. So let’s consider the following:
1 2 3 4 5 6 7 |
template <typename Cond, typename F> auto make_optional_eval(const Cond &cond, F f) -> boost::optional<decltype(f())> { if (!cond) { return {}; } return f(); } auto bar = make_optional_eval(foo, [&]{ return foo->bar; }); |
It cannot be named make_optional, because it could collide with boost::make_optional and trying to resolve it by checking whether the second argument is a callable is a waste of time, because it would prevent the ability of constructing an optional of a callable.
make_optional_eval is simple and very flexible. There is no constraint between the condition and the expression. You can also invoke a method of an object and you can even call sub objects.
1 |
auto biz = make_optional_eval(foo, [&]{ return foo->bar().biz(); }); |