Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between boost optional and std::experimental optional assignment

Usually when a function returns boost::optional I've seen a lot of people returning an empty brace {} to designate an empty value, that works fine and is shorter than returning boost::none.

I tried to do something similar to empty a boost::optional<int>, but when calling the copy assignment operator (or most probably the move assignment op) with an empty brace in the right side, the empty brace is converted to an int and then that value is assigned to the optional, so I end up with the variable set to 0 and not an empty value as I was expecting. Here's an example https://godbolt.org/g/HiF92v, if I try the same with std::experimental::optional I get the result I'm expecting (just replace with std::experimental::optional in the example and you will see that the instruction becomes mov eax, eax).

Also if I try with a different template argument for the boost optional (a non integer type) some compilers compile (with the behavior I'm expecting, an example here http://cpp.sh/5j7n) and others don't. So even for the same lib the behavior is different according to the template arg.

I'd like to understand what is going on here, I know it has something to do with the fact that I'm using a C++14 feature for a library that doesn't consider that into the design. I read the boost/optional header but I got lost in the details, I also tried to study the compiled code without inlining with a similar result.

I'm using gcc 4.9.2 with -std=c++14 and boost 1.57.

btw: I known I should have used boost::optional::reset or boost::none, but I was trying to be consistent with the semantics in the rest of the code base.

like image 837
dlavila Avatar asked Nov 02 '16 23:11

dlavila


People also ask

What is boost :: optional?

Optional provides the class boost::optional , which can be used for optional return values. These are return values from functions that may not always return a result. Example 21.1 illustrates how optional return values are usually implemented without Boost.

What is the use of std :: optional?

std::optional contains the object within itself, depending on where it is stored (stack/data/heap) std::optional makes a copy of the contained object. Monadic functions will be added in C++23 to improve the abstraction in our code by removing the needs of writing boilerplate code.

Is STD optional efficient?

As a conclusion, std::optional is as efficient as a custom type to represent an optional integer value. Don't implement your own type, simply use the standard type.

How do you assign a value to Std optional?

std::optional Operations If you have existing optional object, then you can easily change the contained value by using several operations like emplace , reset , swap , assign. If you assign (or reset) with a nullopt then if the optional contains a value its destructor will be called.


1 Answers

To understand what is going on, consider this example first:

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

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

This results in a compiler error, because the overload resolution is inconclusive: double and int fit equally well. But, if a non-scalar type comes into play, the situation is different:

struct Wrap{};
void fun(int) { puts("f(int)"); }
void fun(Wrap) { puts("f(Wrap)"); }

int main() {
  fun({}); // ok: f(int) selected
}

This is because a scalar is a better match. If, for some reason, I want the same two overloads but at the same time I would like fun({}) to select overload fun(Wrap), I can tweak the definitions a bit:

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)"); }

That is, fun(Wrap) remains unchanged, but the first overload is now a template that takes any T. But with enable_if we constrain it, so that it only works with type int. So, this is quite an 'artificial' template, but it does the job. If I call:

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

The artificial template gets selected. But if I type:

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

The artificial template is still a template, so it is never considered in type deduction in this case, and the only visible overload is fun(Wrap), so it gets selected.

The same trick is employed in std::optional<T>: it does not have an assignment from T. Instead it has a similar artificial assignment template that takes any U, but is later constrained, so that T == U. You can see it in the reference implementation here.

boost::optional<T> has been implemented before C++11, unaware of this 'reset idiom'. Therefore it has a normal assignment from T, and in cases where T happens to be a scalar this assignment from T is preferred. Hence the difference.

Given all that, I think that Boost.Optional has a bug that it does something opposite than std::optional. Even if it is not implementable in Boost.Optional, it should at least fail to compile, in order to avoid run-time surprises.

like image 67
Andrzej Avatar answered Oct 24 '22 07:10

Andrzej