Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Convenience Conversions and Deductions

Unlike std::optional, boost::optional does not offer a number of "convenience" converting constructors, mixed relational operations and deductions for class template parameters.

std::optional oi = 1;                 // OK

std:string_view sv = "hi";
std::optional<std::string> os = sv;   // OK
os == sv;                             // OK

std::optional<std::string> osv;
std::optional<std::string> os2 = osv; // OK
os2 == osv;                           // OK

They are practical, and sometimes stem from the argument for consistency: if (optT && *optT == u) works then (optT == u) should also work.

However, these intelligent convenience functions sometimes produce results that are counter to the programmer intentions and produce silent bugs.

Consider a more complicated example:

Threshold th = /*...*/;
std::optional o = th;
assert (o);

In this code, can we expect that thus initialized optional contains a value? The answer is: it depends on the type of Threshold. It can be defined as:

using Threshold = std::optional<int>;

And then the assertion will fire. This is because in this case the intelligence decides that since we already have an optional, the additional wrapping into a yet another optional is unnecessary.

If we explicitly specify the template type, the situation doesn't get less complicated.

Threshold th;
std::optional<Threshold> o = th;
assert(o);

Can this assertion fire? Now we have two competing constructors:

template <typename U>
optional(U const&);

template <typename U>
optional(optional<U> const&);

Which one will get chosen? Actually, we are lucky, and it is going to be the first one due to concept tricks. But let's try a different example:

Threshold th;
std::optional<Threshold> o = th;
assert(o);
assert(o == th);

Here, the first assertion passes, but the second one fires. This is because there are two competing overloads of the comparison operator:

template <typename T, typename U>
bool operator==(optional<T> const&, U const&);

template <typename T, typename U>
bool operator==(optional<T> const&, optional<U> const&);

And this time there is no concept trickery, so the second overload is chosen, and gives different results: we are comparing an optional object th, which does not contain a value, with an optional object o which does contain a value.

This problem -- that the operations compile, but have runtime behavior counter to programmer's intuition -- gains new significance with the introduction of concepts to C++.

static_assert(std::equality_comparable_with<std::optional<Threshold>, Threshold>);

Concepts have both syntactic constraints and semantic constraints. Syntactic constraints are statically checked by the compiler. For semantic constraints, functions that use the concept trust the programmer that these constraints are met, and if not, this is undefined behavior.

These are problems with std::optional. boost::optional doesn't have these problems, because it does not offer the said convenience operations.

The design principle for boost::optional is not to offer functionality that nicely deduces the programmer intentions in 95% of the cases, and in the remaining 5% renders effects counter to programmer expectations.

Instead, this library recommends using a more verbose syntax that works in 100% of the cases:

Threshold th;
auto o = boost::make_potional(th);   // *always* add a new layer of optionality

return boost::equal_pointees(o, th); // *always* unpack optionals for comparison
return o && *o == th;    // *always* treat the right-hand side argument as value

PrevUpHomeNext