Feb 22, 2021
bsl::optional: An Allocator-Aware Optional Type¶
Summary¶
The latest release of BDE (3.75.1) contains a new type, bsl::optional, that
provides an allocator-aware optional type. This type implements the
proposed standard type std::pmr::optional (P2047)
and is included via the bsl_optional.h header.
For non-allocator-aware types, bsl::optional provides a standard-compliant
implementation of std::optional. In fact, if std::optional is
provided by the platform (e.g., when compiling with GCC with -std=c++17),
bsl::optional publicly inherits from std::optional.
For allocator-aware types on all platforms and for non-allocator-aware types
on platforms that do not provide std::optional, bsl::optional is an
independent implementation.
bdlb::NullableValue has now become a thin wrapper that inherits from
bsl::optional and adapts the interface of bsl::optional to that of
bdlb::NullableValue.
Differences Between std::optional and bsl::optional¶
If bsl::optional is instantiated with a template parameter that is not
an allocator-aware type (i.e., if bslma::UsesBslmaAllocator<T> is false),
it is a standard-conforming implementation of std::optional. For
C++17 compilers, bsl::optional will inherit and derive its
implementation from the platform-supplied std::optional.
If (and only if) bsl::optional is instantiated on an allocator-aware type,
it is an independent implementation that mirrors the standard optional type but
differs from it in these respects:
There are additional overloads for several constructors and
value_ortaking an allocator parameter.There is an additional
get_allocatormethod.The footprint is larger to allow storage of the allocator used by the optional object.
Additional Overloads |
|---|
Constructors |
|
|
|
|
|
|
|
|
|
|
|
Accessors/Manipulators |
|
|
Additional Free Functions |
|---|
|
|
|
Note that:
All template constructors are
explicit.All the constructors with a type
O(for “Other”) only exist ifTis convertible toO.The methods returning
Tby-value (e.g.,value_or) are only present if the compiler guarantees copy-elision (i.e., C++17 and later).
Constructor Documentation¶
Unfortunately, the generated doxygen component documentation for the
constructors of bsl::optional is hard to understand because of the
complex SFINAE constraints on some of the constructors.
Important
We recommend referring to the cpp-reference documentation for optional.
To understand the generated doxygen, the constructor parameters of the form
BSLSTL_OPTIONAL_DECLARE_IF_* express the condition
under which the constructor is available (e.g., BSLSTL_OPTIONAL_DECLARE_IF_SAME(T, ANY_TYPE)
indicates the constructor only exists if the type of the optional being constructed
matches the type of the value being passed as a constructor parameter).
Migrating From bdlb::NullableValue¶
This release preserves backwards compatibility with respect to bdlb::NullableValue
(with one exception, see bdlb::NullableValue Behavior Change: C++03-Only Implicit Conversion to bool)
and, therefore, no code changes are required to build
with the new version of BDE. However, we encourage clients to adopt bsl::optional
in place of bdlb::NullableValue (which will eventually be deprecated).
Since bdlb::NullableValue publicly inherits from bsl::optional, users
may update public APIs that accept a bdlb::NullableValue to instead accept
a bsl::optional without impacting users of that API. For example,
changing the signature of the existingFunction below to use
bsl::optional would be safe:
// BEFORE
void existingFunction(const bdlb::NullableValue<int>&);
// AFTER
void existingFunction(const bsl::optional<int>&);
Comparison of bsl::optional and bdlb::NullableValue¶
bsl::optional and bdlb::NullableValue serve the same purpose and have
corresponding methods, but the corresponding methods often have different
names. The table below shows a mapping of methods of bdlb::NullableValue
to those of bsl::optional (operations that do not have direct counterparts
in bsl::optional are marked as N/A):
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Interoperability Between bsl::optional and bdlb::NullableValue¶
For the most part, interoperability is defined by the inheritance relationship
between these two types. For a template parameter type T, the following
conversions are supported:
C++17 non-allocator-aware
bdlb::NullableValue<T> -> bsl::optional<T> -> std::optional<T>
C++17 Allocator-Aware, and pre-C++17
bdlb::NullableValue<T> -> bsl::optional<T>
bdlb::NullableValue<T> is always implicitly convertible to
bsl::optional<T>, irrespective of whether T is allocator-aware. Both
bdlb::NullableValue<T> and bsl::optional<T> are implicitly convertible
to std::optional<T> when std::optional is available and T is not
allocator-aware.
When instantiated on an allocator-aware type, bsl::optional<T> and
std::optional<T> are distinct, unrelated types. However, bsl::optional<T>
is constructible (and assignable) from std::optional<U> of a compatible type.
The converting constructors are implicit if T is implicitly constructible
from U (which applies when U == T since copy constructors are normally
implicit), and are explicit if construction of T from U is explicit.
The following example examines the interoperability that is afforded (or not)
when initializing a bsl::optional from an std::optional:
void f_string_view(const bsl::optional<bsl::string_view>&);
void f_string (const bsl::optional<bsl::string>&);
std::optional<bsl::string_view> stdOpt;
bsl::optional<bsl::string> bslOpt(stdOpt); // OK - direct initialization considers explicit constructors
// bsl::optional<bsl::string> bslOpt = stdOpt; // error - copy initialization can't use explicit constructor
f_string_view(stdOpt); // OK - implicit construction of bsl::optional<T> from std::optional<T>
// f_string(stdOpt); // error - can't implicitly construct bsl::optional<bsl::string> from std::optional<bsl::string_view>
The following example explores assignment operations between
bsl::optional and std::optional, both for template parameters that are
allocator-aware and template parameters that are not allocator-aware:
std::optional<bsl::string_view> stdNonAA;
bsl::optional<bsl::string_view> bslNonAA;
std::optional<bsl::string> stdAA;
bsl::optional<bsl::string> bslAA;
stdNonAA = bslNonAA; // OK - bsl::optional inherits from std::optional
bslNonAA = stdNonAA; // OK - assignment operator from std::optional
// stdNonAA = bslAA; // error - bsl::optional<bsl::string> does not inherit from std::optional<bsl::string>
bslAA = stdNonAA; // OK - assignment operator from std::optional<U>
stdNonAA = stdAA; // OK - no bsl::optional involved
stdAA = stdNonAA; // OK - no bsl::optional involved
bslNonAA = stdAA; // OK - assignment operator from std::optional<U>
stdAA = bslNonAA; // OK - bsl::optional inherits from std::optional
bslNonAA = bslAA; // OK - no std involved
bslAA = bslNonAA; // OK - no std involved
// stdAA = bslAA; // error - bsl::optional<bsl::string> does not inherit from std::optional<std::string>
bslAA = stdAA; // assignment operator on bsl::optional taking an std::optional
Relational Operator Changes¶
A notable behavioral difference between bsl::optional and
bdlb::NullableValue is that the relational operators for
bdlb::NullableValue are all implemented in terms of operator<
(e.g., operator>= is implemented using !operator< on the contained type).
By contrast, bsl::optional, per the standard definition for optional,
delegates directly to the corresponding operator.
bdlb::NullableValue Behavior Change: C++03-Only Implicit Conversion to bool¶
For standard-compliance, bsl::optional has a conversion operator to
bool, which is inherited by bdlb::NullableValue. The resulting Boolean
value indicates whether the nullable object is “engaged”. This conversion is
explicit with C++11 and later (per the C++ Standard), but is implicit with
C++03 because explicit conversion operators were not supported until C++11.
Note that this implicit conversion on C++03 platforms is implemented using the
“unspecified Boolean type” idiom.
For example, prior to the release of bsl::optional, the following did not
assert in any build mode but it now asserts on C++03 platforms:
typedef bdlb::NullableValue<double> Arbitrary;
assert(!(bsl::is_convertible<Arbitrary, bool>::value));
The result is the same when double is substituted with any other type.