Improve docs

Restructure.
Rewrite old content.
Add new page, on convenience functions
This commit is contained in:
Andrzej Krzemienski
2024-09-18 09:28:55 +02:00
parent f97e43ce13
commit 1825c778ed
13 changed files with 295 additions and 392 deletions

View File

@ -49,9 +49,15 @@ Distributed under the Boost Software License, Version 1.0.
[def __SPACE__ [$images/space.png]] [def __SPACE__ [$images/space.png]]
[def __GO_TO__ [$images/R.png]] [def __GO_TO__ [$images/R.png]]
[/ Common terms ]
[def __UB__ [@https://en.cppreference.com/w/cpp/language/ub ['undefined behavior]]]
[section Introduction] [section Introduction]
Class template `optional` is a wrapper for representing 'optional' (or 'nullable') objects who may not (yet) contain a valid value. Optional objects offer full value semantics; they are good for passing by value and usage inside STL containers. This is a header-only library. Class template `optional` is a wrapper for representing 'optional' (or 'nullable')
objects who may not (yet) contain a valid value. Optional objects offer full value semantics;
they are good for passing by value and usage inside STL containers. This is a header-only C++11 library.
[heading Problem] [heading Problem]
Suppose we want to read a parameter from a config file which represents some integral value, let's call it `"MaxValue"`. It is possible that this parameter is not specified; such situation is no error. It is valid to not specify the parameter and in that case the program is supposed to behave slightly differently. Also, suppose that any possible value of type `int` is a valid value for `"MaxValue"`, so we cannot just use `-1` to represent the absence of the parameter in the config file. Suppose we want to read a parameter from a config file which represents some integral value, let's call it `"MaxValue"`. It is possible that this parameter is not specified; such situation is no error. It is valid to not specify the parameter and in that case the program is supposed to behave slightly differently. Also, suppose that any possible value of type `int` is a valid value for `"MaxValue"`, so we cannot just use `-1` to represent the absence of the parameter in the config file.
@ -75,19 +81,16 @@ This is how you solve it with `boost::optional`:
[endsect] [endsect]
[include 01_quick_start.qbk] [include 01_quick_start.qbk]
[section Tutorial] [section:design Design Overview and Rationale]
[include 10_motivation.qbk]
[include 11_development.qbk] [include 11_development.qbk]
[include 12_when_to_use.qbk] [include 12_relational_operators.qbk]
[include 13_relational_operators.qbk] [include 13_convenience.qbk]
[include 14_monadic_interface.qbk]
[include 15_io.qbk] [include 15_io.qbk]
[include 16_optional_references.qbk] [include 16_optional_references.qbk]
[include 17_in_place_factories.qbk] [include 17_in_place_factories.qbk]
[include 18_gotchas.qbk] [include 18_gotchas.qbk]
[include 19_exception_safety.qbk] [include 19_exception_safety.qbk]
[include 1A_type_requirements.qbk] [include 1A_type_requirements.qbk]
[include 1B_on_performance.qbk]
[endsect] [endsect]
[section:reference Reference] [section:reference Reference]
[include 21_ref_none.qbk] [include 21_ref_none.qbk]
@ -100,6 +103,10 @@ This is how you solve it with `boost::optional`:
[endsect] [endsect]
[include 29_ref_optional_convenience.qbk] [include 29_ref_optional_convenience.qbk]
[endsect] [endsect]
[section:advice Advice]
[include 31_when_to_use.qbk]
[include 32_on_performance.qbk]
[endsect]
[include 90_dependencies.qbk] [include 90_dependencies.qbk]
[include 91_comparison_with_std.qbk] [include 91_comparison_with_std.qbk]
[include 92_relnotes.qbk] [include 92_relnotes.qbk]

View File

@ -10,16 +10,24 @@
] ]
[section Quick Start] [section Quick Overview]
[section Optional return values] [section Optional return values]
Let's write and use a converter function that converts a `std::string` to an `int`. It is possible that for a given string (e.g. `"cat"`) there exists no value of type `int` capable of representing the conversion result. We do not consider such situation an error. We expect that the converter can be used only to check if the conversion is possible. A natural signature for this function can be: Let's write and use a converter function that converts a `std::string` to an `int`.
It is possible that for a given string (e.g. `"cat"`) there exists no value of type
`int` capable of representing the conversion result. We do not consider such
situation an error. We expect that the converter can be used only to check if
the conversion is possible. A natural signature for this function can be:
#include <boost/optional.hpp> #include <boost/optional.hpp>
boost::optional<int> convert(const std::string& text); boost::optional<int> convert(const std::string& text);
All necessary functionality can be included with one header `<boost/optional.hpp>`. The above function signature means that the function can either return a value of type `int` or a flag indicating that no value of `int` is available. This does not indicate an error. It is like one additional value of `int`. This is how we can use our function: All necessary functionality can be included with one header `<boost/optional.hpp>`.
The above function signature means that the function can either return a value
of type `int` or a flag indicating that no value of `int` is available.
This does not indicate an error. It is like one additional value of `int`.
This is how we can use our function:
const std::string& text = /*... */; const std::string& text = /*... */;
boost::optional<int> oi = convert(text); // move-construct boost::optional<int> oi = convert(text); // move-construct
@ -116,29 +124,10 @@ Suppose we want to implement a ['lazy load] optimization. This is because we do
`optional`'s default constructor creates an uninitialized optional. No call to `Resource`'s default constructor is attempted. `Resource` doesn't have to be __STD_DEFAULT_CONSTRUCTIBLE__. In function `getResource` we first check if `resource_` is initialized. This time we do not use the contextual conversion to `bool`, but a comparison with `boost::none`. These two ways are equivalent. Function `emplace` initializes the optional in-place by perfect-forwarding the arguments to the constructor of `Resource`. No copy- or move-construction is involved here. `Resource` doesn't even have to be `MoveConstructible`. `optional`'s default constructor creates an uninitialized optional. No call to `Resource`'s default constructor is attempted. `Resource` doesn't have to be __STD_DEFAULT_CONSTRUCTIBLE__. In function `getResource` we first check if `resource_` is initialized. This time we do not use the contextual conversion to `bool`, but a comparison with `boost::none`. These two ways are equivalent. Function `emplace` initializes the optional in-place by perfect-forwarding the arguments to the constructor of `Resource`. No copy- or move-construction is involved here. `Resource` doesn't even have to be `MoveConstructible`.
[note Function `emplace` is only available on compilers that support rvalue references and variadic templates. If your compiler does not support these features and you still need to avoid any move-constructions, use [link boost_optional.tutorial.in_place_factories In-Place Factories].] [note Function `emplace` is only available on compilers that support rvalue references and variadic templates. If your compiler does not support these features and you still need to avoid any move-constructions, use [link boost_optional.design.in_place_factories In-Place Factories].]
[endsect] [endsect]
[section Bypassing unnecessary default construction]
Suppose we have class `Date`, which does not have a default constructor: there is no good candidate for a default date. We have a function that returns two dates in form of a `boost::tuple`:
boost::tuple<Date, Date> getPeriod();
In other place we want to use the result of `getPeriod`, but want the two dates to be named: `begin` and `end`. We want to implement something like 'multiple return values':
Date begin, end; // Error: no default ctor!
boost::tie(begin, end) = getPeriod();
The second line works already, this is the capability of __BOOST_TUPLE__ library, but the first line won't work. We could set some invented initial dates, but it is confusing and may be an unacceptable cost, given that these values will be overwritten in the next line anyway. This is where `optional` can help:
boost::optional<Date> begin, end;
boost::tie(begin, end) = getPeriod();
It works because inside `boost::tie` a move-assignment from `T` is invoked on `optional<T>`, which internally calls a move-constructor of `T`.
[endsect]
[section Storage in containers] [section Storage in containers]
Suppose you want to ask users to choose some number (an `int`). One of the valid responses is to choose nothing, which is represented by an uninitialized `optional<int>`. You want to make a histogram showing how many times each choice was made. You can use an `std::map`: Suppose you want to ask users to choose some number (an `int`). One of the valid responses is to choose nothing, which is represented by an uninitialized `optional<int>`. You want to make a histogram showing how many times each choice was made. You can use an `std::map`:
@ -157,4 +146,50 @@ which is compared less than any value of `T`.
as it provides specializations for `std::hash`. as it provides specializations for `std::hash`.
[endsect] [endsect]
[section Monadic interface]
The monadic interface of `optional` allows the application of functions
to optional values without resorting to the usage of explicit `if`-statements.
Function `map` takes a function mapping type `T` onto type `U` and maps an `optional<T>`
onto an `optional<U>` using the provided function.
int length(const string& s){ return s.size(); };
optional<string> null{}, thin{""}, word{"word"};
assert (null.map(length) == none);
assert (thin.map(length) == 0);
assert (word.map(length) == 4);
Function `flat_map` is similar, but it requires the function to return an
`optional<V>` for some type `V`. This `optional<V>` becomes the return type of
`flat_map`.
optional<char> first_char(const string& s) {
if (s.empty()) return none;
else return s[0];
};
optional<string> null{}, thin{""}, word{"word"};
assert (null.flat_map(first_char) == none);
assert (thin.flat_map(first_char) == none);
assert (word.flat_map(first_char) == 'w');
These functions can be combined in one expression reflecting a chain of computations:
auto get_contents(path p) -> optional<string>;
auto trim(string) -> string;
auto length(string) -> int;
auto trimmed_size_of(optional<path> p) -> int
{
return p.flat_map(get_contents)
.map(trim)
.map(length)
.value_or(0);
}
[endsect]
[endsect] [endsect]

View File

@ -1,80 +0,0 @@
[/
Boost.Optional
Copyright (c) 2003-2007 Fernando Luis Cacciola Carballal
Copyright (c) 2014 Andrzej Krzemienski
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
]
[section Motivation]
Consider these functions which should return a value but which might not have
a value to return:
* (A) `double sqrt(double n );`
* (B) `char get_async_input();`
* (C) `point polygon::get_any_point_effectively_inside();`
There are different approaches to the issue of not having a value to return.
A typical approach is to consider the existence of a valid return value as a
postcondition, so that if the function cannot compute the value to return, it
has either undefined behavior (and can use assert in a debug build) or uses a
runtime check and throws an exception if the postcondition is violated. This
is a reasonable choice for example, for function (A), because the lack of a
proper return value is directly related to an invalid parameter (out of domain
argument), so it is appropriate to require the callee to supply only parameters
in a valid domain for execution to continue normally.
However, function (B), because of its asynchronous nature, does not fail just
because it can't find a value to return; so it is incorrect to consider such
a situation an error and assert or throw an exception. This function must
return, and somehow, must tell the callee that it is not returning a meaningful
value.
A similar situation occurs with function (C): it is conceptually an error to
ask a ['null-area] polygon to return a point inside itself, but in many
applications, it is just impractical for performance reasons to treat this as
an error (because detecting that the polygon has no area might be too expensive
to be required to be tested previously), and either an arbitrary point
(typically at infinity) is returned, or some efficient way to tell the callee
that there is no such point is used.
There are various mechanisms to let functions communicate that the returned
value is not valid. One such mechanism, which is quite common since it has
zero or negligible overhead, is to use a special value which is reserved to
communicate this. Classical examples of such special values are `EOF`,
`string::npos`, points at infinity, etc...
When those values exist, i.e. the return type can hold all meaningful values
['plus] the ['signal] value, this mechanism is quite appropriate and well known.
Unfortunately, there are cases when such values do not exist. In these cases,
the usual alternative is either to use a wider type, such as `int` in place of
`char`; or a compound type, such as `std::pair<point,bool>`.
Returning a `std::pair<T,bool>`, thus attaching a boolean flag to the result
which indicates if the result is meaningful, has the advantage that can be
turned into a consistent idiom since the first element of the pair can be
whatever the function would conceptually return. For example, the last two
functions could have the following interface:
std::pair<char,bool> get_async_input();
std::pair<point,bool> polygon::get_any_point_effectively_inside();
These functions use a consistent interface for dealing with possibly nonexistent
results:
std::pair<point,bool> p = poly.get_any_point_effectively_inside();
if ( p.second )
flood_fill(p.first);
However, not only is this quite a burden syntactically, it is also error prone
since the user can easily use the function result (first element of the pair)
without ever checking if it has a valid value.
Clearly, we need a better idiom.
[endsect]

View File

@ -2,250 +2,129 @@
Boost.Optional Boost.Optional
Copyright (c) 2003-2007 Fernando Luis Cacciola Carballal Copyright (c) 2003-2007 Fernando Luis Cacciola Carballal
Copyright (c) 2024 andrzej Krzemieński
Distributed under the Boost Software License, Version 1.0. Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at (See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt) http://www.boost.org/LICENSE_1_0.txt)
] ]
[section Design Overview] [section Design Goals]
[section The models] In C++ you can create an automatic object of a scalar type, and manipulate it,
without assigning it the initial value.
In C++, we can ['declare] an object (a variable) of type `T`, and we can give this {
variable an ['initial value] (through an ['initializer]. (cf. 8.5)). int i; // indeterminate value
When a declaration includes a non-empty initializer (an initial value is given), populate(&i);
it is said that the object has been initialized. cout << i;
If the declaration uses an empty initializer (no initial value is given), and }
neither default nor value initialization applies, it is said that the object is
[*uninitialized]. Its actual value exist but has an ['indeterminate initial value]
(cf. 8.5/11).
`optional<T>` intends to formalize the notion of initialization (or lack of it)
allowing a program to test whether an object has been initialized and stating
that access to the value of an uninitialized object is undefined behavior. That
is, when a variable is declared as `optional<T>` and no initial value is given,
the variable is ['formally] uninitialized. A formally uninitialized optional object
has conceptually no value at all and this situation can be tested at runtime. It
is formally ['undefined behavior] to try to access the value of an uninitialized
optional. An uninitialized optional can be assigned a value, in which case its initialization state changes to initialized. Furthermore, given the formal
treatment of initialization states in optional objects, it is even possible to
reset an optional to ['uninitialized].
In C++ there is no formal notion of uninitialized objects, which means that Such an object is said to have ['indeterminate value]. If you subsequently
objects always have an initial value even if indeterminate. assign a proper value to the object, all is fine; but if the program tries to
As discussed on the previous section, this has a drawback because you need read an indeterminate value, this is ['undefined behavior'], and since C++26
additional information to tell if an object has been effectively initialized. this is ['erroneous behavior].
One of the typical ways in which this has been historically dealt with is via In any case, the program is now likely to do something else than what the
a special value: `EOF`, `npos`, -1, etc... This is equivalent to adding the programmer intended. In case you have some object `i`, and you do not know if it
special value to the set of possible values of a given type. This super set of has an indeterminate value, or a normal, intended, value, there is no way to
`T` plus some ['nil_t]—where `nil_t` is some stateless POD—can be modeled in modern check it, because the checking would require reading the value.
languages as a [*discriminated union] of T and nil_t. Discriminated unions are
often called ['variants]. A variant has a ['current type], which in our case is either
`T` or `nil_t`.
Using the __BOOST_VARIANT__ library, this model can be implemented in terms of `boost::variant<T,nil_t>`.
There is precedent for a discriminated union as a model for an optional value:
the __HASKELL__ [*Maybe] built-in type constructor. Thus, a discriminated union
`T+nil_t` serves as a conceptual foundation.
A `variant<T,nil_t>` follows naturally from the traditional idiom of extending This is one of the primary problems that `optional` was intended to address: so
the range of possible values adding an additional sentinel value with the that you may have a type that knows whether it has been assigned a proper value,
special meaning of ['Nothing]. However, this additional ['Nothing] value is largely and it can tell you that if requested.
irrelevant for our purpose since our goal is to formalize the notion of
uninitialized objects and, while a special extended value can be used to convey
that meaning, it is not strictly necessary in order to do so.
The observation made in the last paragraph about the irrelevant nature of the In the case of type `int` the internal representation of such a class could be:
additional `nil_t` with respect to [_purpose] of `optional<T>` suggests an
alternative model: a ['container] that either has a value of `T` or nothing.
As of this writing I don't know of any precedent for a variable-size class OptionalInt
fixed-capacity (of 1) stack-based container model for optional values, yet I {
believe this is the consequence of the lack of practical implementations of bool _has_value = false;
such a container rather than an inherent shortcoming of the container model. int _value;
};
In any event, both the discriminated-union or the single-element container In the general case, the internal representation is something equivalent to:
models serve as a conceptual ground for a class representing optional—i.e.
possibly uninitialized—objects.
For instance, these models show the ['exact] semantics required for a wrapper
of optional values:
Discriminated-union: template <typename T>
class Optional
{
bool _has_value = false;
alignas(T) char _value [sizeof(T)];
};
* [*deep-copy] semantics: copies of the variant implies copies of the value. Next, because we need to pass around these "optional" `int`s as normal `int`s,
* [*deep-relational] semantics: comparisons between variants matches both like returning them from functions, when copying, we need to copy `_has_value`,
current types and values which indicates whether we have the value or not, and, if we do have value, and
* If the variant's current type is `T`, it is modeling an ['initialized] optional. only then, to also copy `_value`.
* If the variant's current type is not `T`, it is modeling an ['uninitialized]
optional.
* Testing if the variant's current type is `T` models testing if the optional
is initialized
* Trying to extract a `T` from a variant when its current type is not `T`, models
the undefined behavior of trying to access the value of an uninitialized optional
Single-element container: This means that our type requires ['deep copy] semantics.
* [*deep-copy] semantics: copies of the container implies copies of the value. [note
* [*deep-relational] semantics: comparisons between containers compare container This is a C++ equivalent of a
size and if match, contained value [@https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-Maybe.html Maybe]
* If the container is not empty (contains an object of type `T`), it is modeling monad in [@http://www.haskell.org/ Haskell].
an ['initialized] optional.
* If the container is empty, it is modeling an ['uninitialized] optional.
* Testing if the container is empty models testing if the optional is
initialized
* Trying to extract a `T` from an empty container models the undefined behavior
of trying to access the value of an uninitialized optional
[endsect]
[section The semantics]
Objects of type `optional<T>` are intended to be used in places where objects of
type `T` would but which might be uninitialized. Hence, `optional<T>`'s purpose is
to formalize the additional possibly uninitialized state.
From the perspective of this role, `optional<T>` can have the same operational
semantics of `T` plus the additional semantics corresponding to this special
state.
As such, `optional<T>` could be thought of as a ['supertype] of `T`. Of course, we
can't do that in C++, so we need to compose the desired semantics using a
different mechanism.
Doing it the other way around, that is, making `optional<T>` a ['subtype] of `T`
is not only conceptually wrong but also impractical: it is not allowed to
derive from a non-class type, such as a built-in type.
We can draw from the purpose of `optional<T>` the required basic semantics:
* [*Default Construction:] To introduce a formally uninitialized wrapped
object.
* [*Direct Value Construction via copy:] To introduce a formally initialized
wrapped object whose value is obtained as a copy of some object.
* [*Deep Copy Construction:] To obtain a new yet equivalent wrapped object.
* [*Direct Value Assignment (upon initialized):] To assign a value to the
wrapped object.
* [*Direct Value Assignment (upon uninitialized):] To initialize the wrapped
object with a value obtained as a copy of some object.
* [*Assignment (upon initialized):] To assign to the wrapped object the value
of another wrapped object.
* [*Assignment (upon uninitialized):] To initialize the wrapped object with
value of another wrapped object.
* [*Deep Relational Operations (when supported by the type T):] To compare
wrapped object values taking into account the presence of uninitialized states.
* [*Value access:] To unwrap the wrapped object.
* [*Initialization state query:] To determine if the object is formally
initialized or not.
* [*Swap:] To exchange wrapped objects. (with whatever exception safety
guarantees are provided by `T`'s swap).
* [*De-initialization:] To release the wrapped object (if any) and leave the
wrapper in the uninitialized state.
Additional operations are useful, such as converting constructors and
converting assignments, in-place construction and assignment, and safe
value access via a pointer to the wrapped object or null.
[endsect]
[section The Interface]
Since the purpose of optional is to allow us to use objects with a formal
uninitialized additional state, the interface could try to follow the
interface of the underlying `T` type as much as possible. In order to choose
the proper degree of adoption of the native `T` interface, the following must
be noted: Even if all the operations supported by an instance of type `T` are
defined for the entire range of values for such a type, an `optional<T>`
extends such a set of values with a new value for which most
(otherwise valid) operations are not defined in terms of `T`.
Furthermore, since `optional<T>` itself is merely a `T` wrapper (modeling a `T`
supertype), any attempt to define such operations upon uninitialized optionals
will be totally artificial w.r.t. `T`.
This library chooses an interface which follows from `T`'s interface only for
those operations which are well defined (w.r.t the type `T`) even if any of the
operands are uninitialized. These operations include: construction,
copy-construction, assignment, swap and relational operations.
For the value access operations, which are undefined (w.r.t the type `T`) when
the operand is uninitialized, a different interface is chosen (which will be
explained next).
Also, the presence of the possibly uninitialized state requires additional
operations not provided by `T` itself which are supported by a special interface.
[heading Lexically-hinted Value Access in the presence of possibly
uninitialized optional objects: The operators * and ->]
A relevant feature of a pointer is that it can have a [*null pointer value].
This is a ['special] value which is used to indicate that the pointer is not
referring to any object at all. In other words, null pointer values convey
the notion of nonexistent objects.
This meaning of the null pointer value allowed pointers to became a ['de
facto] standard for handling optional objects because all you have to do
to refer to a value which you don't really have is to use a null pointer
value of the appropriate type. Pointers have been used for decades—from
the days of C APIs to modern C++ libraries—to ['refer] to optional (that is,
possibly nonexistent) objects; particularly as optional arguments to a
function, but also quite often as optional data members.
The possible presence of a null pointer value makes the operations that
access the pointee's value possibly undefined, therefore, expressions which
use dereference and access operators, such as: `( *p = 2 )` and `( p->foo() )`,
implicitly convey the notion of optionality, and this information is tied to
the ['syntax] of the expressions. That is, the presence of operators `*` and `->`
tell by themselves —without any additional context— that the expression will
be undefined unless the implied pointee actually exist.
Such a ['de facto] idiom for referring to optional objects can be formalized
in the form of a concept: the __OPTIONAL_POINTEE__ concept.
This concept captures the syntactic usage of operators `*`, `->` and
contextual conversion to `bool` to convey the notion of optionality.
However, pointers are good to [_refer] to optional objects, but not particularly
good to handle the optional objects in all other respects, such as initializing
or moving/copying them. The problem resides in the shallow-copy of pointer
semantics: if you need to effectively move or copy the object, pointers alone
are not enough. The problem is that copies of pointers do not imply copies of
pointees. For example, as was discussed in the motivation, pointers alone
cannot be used to return optional objects from a function because the object
must move outside from the function and into the caller's context.
A solution to the shallow-copy problem that is often used is to resort to
dynamic allocation and use a smart pointer to automatically handle the details
of this. For example, if a function is to optionally return an object `X`, it can
use `shared_ptr<X>` as the return value. However, this requires dynamic allocation
of `X`. If `X` is a built-in or small POD, this technique is very poor in terms of
required resources. Optional objects are essentially values so it is very
convenient to be able to use automatic storage and deep-copy semantics to
manipulate optional values just as we do with ordinary values. Pointers do
not have this semantics, so are inappropriate for the initialization and
transport of optional values, yet are quite convenient for handling the access
to the possible undefined value because of the idiomatic aid present in the
__OPTIONAL_POINTEE__ concept incarnated by pointers.
[heading Optional<T> as a model of OptionalPointee]
For value access operations `optional<>` uses operators `*` and `->` to
lexically warn about the possibly uninitialized state appealing to the
familiar pointer semantics w.r.t. to null pointers.
[caution
However, it is particularly important to note that `optional<>` objects
are not pointers. [_`optional<>` is not, and does not model, a pointer].
] ]
For instance, `optional<>` does not have shallow-copy so does not alias:
two different optionals never refer to the ['same] value unless `T` itself is
a reference (but may have ['equivalent] values).
The difference between an `optional<T>` and a pointer must be kept in mind,
particularly because the semantics of relational operators are different:
since `optional<T>` is a value-wrapper, relational operators are deep: they
compare optional values; but relational operators for pointers are shallow:
they do not compare pointee values.
As a result, you might be able to replace `optional<T>` by `T*` on some
situations but not always. Specifically, on generic code written for both,
you cannot use relational operators directly, and must use the template
functions __FUNCTION_EQUAL_POINTEES__ and __FUNCTION_LESS_POINTEES__ instead.
[endsect] [endsect]
[section:iface Interface Design]
One part of the interface is for modifying and setting the initial state of
the object. It has to be able to say that
* we want to store a specific value of type `T`,
* we want to store no value.
The default constructor stores no value. Other than that, we require that
the assignment and construction from a `T` reflects the former, while assignment
and construction of a special ['tag] value `none` reflect the latter need.
optional<int> o1; // contains no value
optional<int> o2 = 2; // contains value 2
optional<int> o3 = none; // contains no value
o1 = 1; // assign value 1
o2 = none; // assign a no-value
o3 = {}; // assign a no-value
[heading Inspecting the State]
Inspecting the state of an optional object requires two steps:
* check if we have the value or not,
* if so, read the stored value.
This 'procedure' is characteristic of inspecting pointers in C++, therefore the
pointer-like syntax was chosen to represent this.
void inspect (optional<string> os)
{
if (os) { // contextual conversion to `bool`
read_string(*os); // `operator*` to access the stored value
read_int(os->size()); // `operator->` as shortcut for accessing members
}
}
Also, similarly to pointers, if you access the value when it is not there,
you trigger __UB__.
This library detects and reports it via
[@../../../assert/assert.html `BOOST_ASSERT()`]. This common property of
pointers and `optional<>` has been formalized into a concept __OPTIONAL_POINTEE__.
However, there is also the counter-intuitive part. All pointers embed ['shallow-copy]
semantics: when you copy a pointer, the pointed-to object stays at the same location
and you can access it via either of the pointers. This is unlike optional objects
where the represented value is copied along.
[caution
Optional objects are not pointers.
]
There is a similar difference in relational operations: they compare deeply for
`optional<>`, while they are shallow for pointers.
[note
When you need a deep relational operations that work uniformly for `optional<>`
and pointers in generic contexts, use functions
[@../../../utility/OptionalPointee.html#equal `equal_pointees()`] and
[@../../../utility/OptionalPointee.html#less `less_pointees()`].
]
[endsect] [endsect]

102
doc/13_convenience.qbk Normal file
View File

@ -0,0 +1,102 @@

[section 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, ans 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 __UB__.
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
[endsect]

View File

@ -1,45 +0,0 @@
[section Monadic interface]
The monadic interface of `optional` allows the application of functions
to optional values without resorting to the usage of explicit `if`-statements.
Function `map` takes a function mapping type `T` onto type `U` and maps an `optional<T>`
onto an `optional<U>` using the provided function.
int length(const string& s){ return s.size(); };
optional<string> null{}, thin{""}, word{"word"};
assert (null.map(length) == none);
assert (thin.map(length) == 0);
assert (word.map(length) == 4);
Function `flat_map` is similar, but it requires the function to return an
`optional<V>` for some type `V`. This `optional<V>` becomes the return type of
`flat_map`.
optional<char> first_char(const string& s) {
if (s.empty()) return none;
else return s[0];
};
optional<string> null{}, thin{""}, word{"word"};
assert (null.flat_map(first_char) == none);
assert (thin.flat_map(first_char) == none);
assert (word.flat_map(first_char) == 'w');
These functions can be combined in one expression reflecting a chain of computations:
auto get_contents(path p) -> optional<string>;
auto trim(string) -> string;
auto length(string) -> int;
auto trimmed_size_of(optional<path> p) -> int
{
return p.flat_map(get_contents)
.map(trim)
.map(length)
.value_or(0);
}
[endsect]

View File

@ -23,7 +23,12 @@ will nonetheless refer to the same object.
* Value-access will actually provide access to the referenced object * Value-access will actually provide access to the referenced object
rather than the reference itself. rather than the reference itself.
[caution On compilers that do not conform to Standard C++ rules of reference binding, some operations on optional references are disabled in order to prevent subtle bugs. For more details see [link boost_optional.dependencies_and_portability.optional_reference_binding Dependencies and Portability section].] [caution
On compilers that do not conform to Standard C++ rules of reference binding,
some operations on optional references are disabled in order to prevent subtle
bugs. For more details see
[link boost_optional.dependencies_and_portability.optional_reference_binding Dependencies and Portability section].
]
[heading Rvalue references] [heading Rvalue references]
@ -45,7 +50,7 @@ the first time) to the object. Clearly, there is no other choice.
optional<int&> orb(x) ; optional<int&> orb(x) ;
ora = orb ; // now 'ora' is bound to 'x' through 'rx' ora = orb ; // now 'ora' is bound to 'x' through 'rx'
*ora = 2 ; // Changes value of 'x' through 'ora' *ora = 2 ; // Changes value of 'x' through 'ora'
assert(x==2); assert(x==2);
If you assign to a bare C++ reference, the assignment is forwarded to the If you assign to a bare C++ reference, the assignment is forwarded to the
referenced object; its value changes but the reference is never rebound. referenced object; its value changes but the reference is never rebound.
@ -71,8 +76,8 @@ bare C++ references.
optional<int&> orb(rb) ; optional<int&> orb(rb) ;
ora = orb ; // 'ora' is rebound to 'b' ora = orb ; // 'ora' is rebound to 'b'
*ora = 3 ; // Changes value of 'b' (not 'a') *ora = 3 ; // Changes value of 'b' (not 'a')
assert(a==1); assert(a==1);
assert(b==3); assert(b==3);
[heading Rationale] [heading Rationale]

View File

@ -7,8 +7,8 @@ The very minimum requirement of `optional<T>` is that `T` is a complete type and
assert(o == none); // check if initialized assert(o == none); // check if initialized
assert(!o); // assert(!o); //
o.value(); // always throws o.value(); // always throws
But this is practically useless. In order for `optional<T>` to be able to do anything useful and offer all the spectrum of ways of accessing the contained value, `T` needs to have at least one accessible constructor. In that case you need to initialize the optional object with function `emplace()`, or if your compiler does not support it, resort to [link boost_optional.tutorial.in_place_factories In-Place Factories]: But this is practically useless. In order for `optional<T>` to be able to do anything useful and offer all the spectrum of ways of accessing the contained value, `T` needs to have at least one accessible constructor. In that case you need to initialize the optional object with function `emplace()`, or if your compiler does not support it, resort to [link boost_optional.design.in_place_factories In-Place Factories]:
optional<T> o; optional<T> o;
o.emplace("T", "ctor", "params"); o.emplace("T", "ctor", "params");
@ -17,13 +17,13 @@ If `T` is __MOVE_CONSTRUCTIBLE__, `optional<T>` is also __MOVE_CONSTRUCTIBLE__ a
optional<T> o = make_T(); optional<T> o = make_T();
optional<T> p = optional<T>(); optional<T> p = optional<T>();
If `T` is __COPY_CONSTRUCTIBLE__, `optional<T>` is also __COPY_CONSTRUCTIBLE__ and can be easily initialized from an lvalue of type `T`: If `T` is __COPY_CONSTRUCTIBLE__, `optional<T>` is also __COPY_CONSTRUCTIBLE__ and can be easily initialized from an lvalue of type `T`:
T v = make_T(); T v = make_T();
optional<T> o = v; optional<T> o = v;
optional<T> p = o; optional<T> p = o;
If `T` is not `MoveAssignable`, it is still possible to reset the value of `optional<T>` using function `emplace()`: If `T` is not `MoveAssignable`, it is still possible to reset the value of `optional<T>` using function `emplace()`:
optional<const T> o = make_T(); optional<const T> o = make_T();

View File

@ -281,7 +281,7 @@ factory.
* [*Postconditions: ] `*this` is [_initialized] and its value is ['directly given] * [*Postconditions: ] `*this` is [_initialized] and its value is ['directly given]
from the factory `f` (i.e., the value [_is not copied]). from the factory `f` (i.e., the value [_is not copied]).
* [*Throws:] Whatever the `T` constructor called by the factory throws. * [*Throws:] Whatever the `T` constructor called by the factory throws.
* [*Notes:] See [link boost_optional.tutorial.in_place_factories In-Place Factories] * [*Notes:] See [link boost_optional.design.in_place_factories In-Place Factories]
* [*Exception Safety:] Exceptions can only be thrown during the call to * [*Exception Safety:] Exceptions can only be thrown during the call to
the `T` constructor used by the factory; in that case, this constructor has the `T` constructor used by the factory; in that case, this constructor has
no effect. no effect.
@ -524,7 +524,7 @@ factory.
* [*Postconditions: ] `*this` is [_initialized] and its value is ['directly given] * [*Postconditions: ] `*this` is [_initialized] and its value is ['directly given]
from the factory `f` (i.e., the value [_is not copied]). from the factory `f` (i.e., the value [_is not copied]).
* [*Throws:] Whatever the `T` constructor called by the factory throws. * [*Throws:] Whatever the `T` constructor called by the factory throws.
* [*Notes:] See [link boost_optional.tutorial.in_place_factories In-Place Factories] * [*Notes:] See [link boost_optional.design.in_place_factories In-Place Factories]
* [*Exception Safety:] Exceptions can only be thrown during the call to * [*Exception Safety:] Exceptions can only be thrown during the call to
the `T` constructor used by the factory; in that case, the `optional` object the `T` constructor used by the factory; in that case, the `optional` object
will be reset to be ['uninitialized]. will be reset to be ['uninitialized].
@ -963,7 +963,7 @@ __SPACE__
* [*Postconditions:] `bool(*this) == bool(rhs)`. * [*Postconditions:] `bool(*this) == bool(rhs)`.
* [*Notes:] This behaviour is called ['rebinding semantics]. See [link boost_optional.tutorial.optional_references.rebinding_semantics_for_assignment_of_optional_references here] for details. * [*Notes:] This behaviour is called ['rebinding semantics]. See [link boost_optional.design.optional_references.rebinding_semantics_for_assignment_of_optional_references here] for details.
* [*Example:] * [*Example:]
`` ``

View File

@ -57,7 +57,7 @@ On compilers that do not support rvalue references, each of these functions is s
template<class Arg> void emplace(Arg& arg); template<class Arg> void emplace(Arg& arg);
void emplace(); void emplace();
This workaround addresses about 40% of all use cases. If this is insufficient, you need to resort to using [link boost_optional.tutorial.in_place_factories In-Place Factories]. This workaround addresses about 40% of all use cases. If this is insufficient, you need to resort to using [link boost_optional.design.in_place_factories In-Place Factories].
[endsect] [endsect]
[section Optional Reference Binding][#optional_reference_binding] [section Optional Reference Binding][#optional_reference_binding]