blob: 07f1039440a0f4a8eb1f5c58e21c1e4647638b55 [file] [log] [blame]
[section Optional references]
This library allows the template parameter `T` to be of reference type:
`T&`, and to some extent, `T const&`.
However, since references are not real objects some restrictions apply and
some operations are not available in this case:
* Converting constructors
* Converting assignment
* InPlace construction
* InPlace assignment
* Value-access via pointer
Also, even though `optional<T&>` treats it wrapped pseudo-object much as
a real value, a true real reference is stored so aliasing will ocurr:
* Copies of `optional<T&>` will copy the references but all these references
will nonetheless reefer to the same object.
* Value-access will actually provide access to the referenced object
rather than the reference itself.
[endsect]
[section Rebinding semantics for assignment of optional references]
If you assign to an ['uninitialized ] `optional<T&>` the effect is to bind (for
the first time) to the object. Clearly, there is no other choice.
int x = 1 ;
int& rx = x ;
optional<int&> ora ;
optional<int&> orb(x) ;
ora = orb ; // now 'ora' is bound to 'x' through 'rx'
*ora = 2 ; // Changes value of 'x' through 'ora'
assert(x==2);
If you assign to a bare C++ reference, the assignment is forwarded to the
referenced object; it's value changes but the reference is never rebound.
int a = 1 ;
int& ra = a ;
int b = 2 ;
int& rb = b ;
ra = rb ; // Changes the value of 'a' to 'b'
assert(a==b);
b = 3 ;
assert(ra!=b); // 'ra' is not rebound to 'b'
Now, if you assign to an ['initialized ] `optional<T&>`, the effect is to
[*rebind] to the new object instead of assigning the referee. This is unlike
bare C++ references.
int a = 1 ;
int b = 2 ;
int& ra = a ;
int& rb = b ;
optional<int&> ora(ra) ;
optional<int&> orb(rb) ;
ora = orb ; // 'ora' is rebound to 'b'
*ora = 3 ; // Changes value of 'b' (not 'a')
assert(a==1);
assert(b==3);
[heading Rationale]
Rebinding semantics for the assignment of ['initialized ] `optional` references has
been chosen to provide [*consistency among initialization states] even at the
expense of lack of consistency with the semantics of bare C++ references.
It is true that `optional<U>` strives to behave as much as possible as `U`
does whenever it is initialized; but in the case when `U` is `T&`, doing so would
result in inconsistent behavior w.r.t to the lvalue initialization state.
Imagine `optional<T&>` forwarding assignment to the referenced object (thus
changing the referenced object value but not rebinding), and consider the
following code:
optional<int&> a = get();
int x = 1 ;
int& rx = x ;
optional<int&> b(rx);
a = b ;
What does the assignment do?
If `a` is ['uninitialized], the answer is clear: it binds to `x` (we now have
another reference to `x`).
But what if `a` is already ['initialized]? it would change the value of the
referenced object (whatever that is); which is inconsistent with the other
possible case.
If `optional<T&>` would assign just like `T&` does, you would never be able to
use Optional's assignment without explicitly handling the previous
initialization state unless your code is capable of functioning whether
after the assignment, `a` aliases the same object as `b` or not.
That is, you would have to discriminate in order to be consistency.
If in your code rebinding to another object is not an option, then is very
likely that binding for the fist time isn't either. In such case, assignment
to an ['uninitialized ] `optional<T&>` shall be prohibited. It is quite possible
that in such scenario the precondition that the lvalue must be already
initialized exist. If it doesn't, then binding for the first time is OK
while rebinding is not which is IMO very unlikely.
In such scenario, you can assign the value itself directly, as in:
assert(!!opt);
*opt=value;
[endsect]
[section In-Place Factories]
One of the typical problems with wrappers and containers is that their
interfaces usually provide an operation to initialize or assign the
contained object as a copy of some other object. This not only requires the
underlying type to be __COPY_CONSTRUCTIBLE__, but also requires the existence of
a fully constructed object, often temporary, just to follow the copy from:
struct X
{
X ( int, std:::string ) ;
} ;
class W
{
X wrapped_ ;
public:
W ( X const& x ) : wrapped_(x) {}
} ;
void foo()
{
// Temporary object created.
W ( X(123,"hello") ) ;
}
A solution to this problem is to support direct construction of the
contained object right in the container's storage.
In this scheme, the user only needs to supply the arguments to the
constructor to use in the wrapped object construction.
class W
{
X wrapped_ ;
public:
W ( X const& x ) : wrapped_(x) {}
W ( int a0, std::string a1) : wrapped_(a0,a1) {}
} ;
void foo()
{
// Wrapped object constructed in-place
// No temporary created.
W (123,"hello") ;
}
A limitation of this method is that it doesn't scale well to wrapped
objects with multiple constructors nor to generic code were the constructor
overloads are unknown.
The solution presented in this library is the family of [*InPlaceFactories]
and [*TypedInPlaceFactories].
These factories are a family of classes which encapsulate an increasing
number of arbitrary constructor parameters and supply a method to construct
an object of a given type using those parameters at an address specified by
the user via placement new.
For example, one member of this family looks like:
template<class T,class A0, class A1>
class TypedInPlaceFactory2
{
A0 m_a0 ; A1 m_a1 ;
public:
TypedInPlaceFactory2( A0 const& a0, A1 const& a1 ) : m_a0(a0), m_a1(a1) {}
void construct ( void* p ) { new (p) T(m_a0,m_a1) ; }
} ;
A wrapper class aware of this can use it as:
class W
{
X wrapped_ ;
public:
W ( X const& x ) : wrapped_(x) {}
W ( TypedInPlaceFactory2 const& fac ) { fac.construct(&wrapped_) ; }
} ;
void foo()
{
// Wrapped object constructed in-place via a TypedInPlaceFactory.
// No temporary created.
W ( TypedInPlaceFactory2<X,int,std::string>(123,"hello")) ;
}
The factories are divided in two groups:
* [_TypedInPlaceFactories]: those which take the target type as a primary
template parameter.
* [_InPlaceFactories]: those with a template `construct(void*)` member
function taking the target type.
Within each group, all the family members differ only in the number of
parameters allowed.
This library provides an overloaded set of helper template functions to
construct these factories without requiring unnecessary template parameters:
template<class A0,...,class AN>
InPlaceFactoryN <A0,...,AN> in_place ( A0 const& a0, ..., AN const& aN) ;
template<class T,class A0,...,class AN>
TypedInPlaceFactoryN <T,A0,...,AN> in_place ( T const& a0, A0 const& a0, ..., AN const& aN) ;
In-place factories can be used generically by the wrapper and user as follows:
class W
{
X wrapped_ ;
public:
W ( X const& x ) : wrapped_(x) {}
template< class InPlaceFactory >
W ( InPlaceFactory const& fac ) { fac.template <X>construct(&wrapped_) ; }
} ;
void foo()
{
// Wrapped object constructed in-place via a InPlaceFactory.
// No temporary created.
W ( in_place(123,"hello") ) ;
}
The factories are implemented in the headers: __IN_PLACE_FACTORY_HPP__ and __TYPED_IN_PLACE_FACTORY_HPP__
[endsect]
[section A note about optional<bool>]
`optional<bool>` should be used with special caution and consideration.
First, it is functionally similar to a tristate boolean (false,maybe,true)
such as __BOOST_TRIBOOL__ except that in a tristate boolean, the maybe state
[_represents a valid value], unlike the corresponding state of an uninitialized
`optional<bool>`.
It should be carefully considered if an `optional<bool>` instead of a `tribool`
is really needed.
Second, `optional<>` provides an implicit conversion to `bool`. This
conversion refers to the initialization state and not to the contained value.
Using `optional<bool>` can lead to subtle errors due to the implicit `bool`
conversion:
void foo ( bool v ) ;
void bar()
{
optional<bool> v = try();
// The following intended to pass the value of 'v' to foo():
foo(v);
// But instead, the initialization state is passed
// due to a typo: it should have been foo(*v).
}
The only implicit conversion is to `bool`, and it is safe in the sense that
typical integral promotions don't apply (i.e. if `foo()` takes an `int`
instead, it won't compile).
[endsect]
[section Exception Safety Guarantees]
Because of the current implementation (see [link boost_optional.implementation_notes Implementation Notes]), all of the assignment methods:
* `optional<T>::operator= ( optional<T> const& )`
* `optional<T>::operator= ( T const& )`
* `template<class U> optional<T>::operator= ( optional<U> const& )`
* `template<class InPlaceFactory> optional<T>::operator= ( InPlaceFactory const& )`
* `template<class TypedInPlaceFactory> optional<T>::operator= ( TypedInPlaceFactory const& ) `
* `optional<T>:::reset ( T const&)`
Can only ['guarantee] the [_basic exception safety]: The lvalue optional is
left [_uninitialized] if an exception is thrown (any previous value is ['first]
destroyed using `T::~T()`)
On the other hand, the ['uninitializing] methods:
* `optional<T>::operator= ( detail::none_t )`
* `optional<T>::reset()`
Provide the no-throw guarantee (assuming a no-throw `T::~T()`)
However, since `optional<>` itself doesn't throw any exceptions, the only
source for exceptions here are `T`'s constructor, so if you know the exception
guarantees for `T::T ( T const& )`, you know that `optional`'s assignment and
reset has the same guarantees.
//
// Case 1: Exception thrown during assignment.
//
T v0(123);
optional<T> opt0(v0);
try
{
T v1(456);
optional<T> opt1(v1);
opt0 = opt1 ;
// If no exception was thrown, assignment succeeded.
assert( *opt0 == v1 ) ;
}
catch(...)
{
// If any exception was thrown, 'opt0' is reset to uninitialized.
assert( !opt0 ) ;
}
//
// Case 2: Exception thrown during reset(v)
//
T v0(123);
optional<T> opt(v0);
try
{
T v1(456);
opt.reset ( v1 ) ;
// If no exception was thrown, reset succeeded.
assert( *opt == v1 ) ;
}
catch(...)
{
// If any exception was thrown, 'opt' is reset to uninitialized.
assert( !opt ) ;
}
[heading Swap]
`void swap( optional<T>&, optional<T>& )` has the same exception guarantee
as `swap(T&,T&)` when both optionals are initialized.
If only one of the optionals is initialized, it gives the same ['basic]
exception guarantee as `optional<T>::reset( T const& )` (since
`optional<T>::reset()` doesn't throw).
If none of the optionals is initialized, it has no-throw guarantee
since it is a no-op.
[endsect]
[section Type requirements]
In general, `T` must be __COPY_CONSTRUCTIBLE__ and have a no-throw destructor.
The copy-constructible requirement is not needed if [*InPlaceFactories] are used.
`T` [_is not] required to be __SGI_DEFAULT_CONSTRUCTIBLE__.
[endsect]