BDE 4.14.0 Production release
|
Provide a variant (discriminated union
-like) type.
This component provides: (1) a variant class template, bdlb::Variant
, that can store an instance of one of up to some (implementation-defined) number of parameter types (currently 20), (2) several variant class templates that accommodate a fixed number (from 2 to 19) of types, bdlb::Variant2
, bdlb::Variant3
, bdlb::Variant4
, bdlb::Variant5
, bdlb::Variant6
, bdlb::Variant7
, bdlb::Variant8
, bdlb::Variant9
, bdlb::Variant10
, bdlb::Variant11
, bdlb::Variant12
, bdlb::Variant13
, bdlb::Variant14
, bdlb::Variant15
, bdlb::Variant16
, bdlb::Variant17
, bdlb::Variant18
, and bdlb::Variant19
, and (3) a final variant class template, bdlb::VariantImp
, whose supported types are specified via a bslmf::TypeList
. A variant (of any of the aforementioned classes) can hold any one of the types defined in its signature at any point in time. Clients can retrieve the value and type that a variant currently holds, assign a new value to the variant, or apply a visitor to the variant. A visitor's action is based on the value and type the variant currently holds. Assigning a value of a new type destroys the object of the old type and constructs the new value by copy constructing the supplied value.
When the number (N
) of types that needs to be supported is known, it is better to use the bdlb::VariantN
templates that use an identical implementation, but generate shorter symbols and debugging information due to the lack of defaulted template argument types. Note that bdlb::VariantN<T1, ..., TN>
and bdlb::Variant<T1, ..., TN>
are, nevertheless, distinct types.
When the variant types are (directly) supplied as a type list (of type bslmf::TypeList
), the type bdlb::VariantImp<TYPELIST>
can be used in place of:
Lastly, move constructors (taking an optional allocator) and move-assignment operators are also provided. Note that move semantics are emulated with C++03 compilers.
The bdlb::Variant
class, when default constructed, does not hold a value and isUnset
returns true
. This state is the same as that of a bdlb::Variant
that is reset by the reset
method.
bdlb::Variant
provides an apply
method that implements the visitor design pattern. apply
accepts a visitor (functor) that provides an operator()
that is invoked with the value that the variant currently holds.
Note, that visitor must satisfy the following requirements:
operator()
must be callable with any of the types that might be contained in the variant.apply
methods (but not applyRaw
) the visitor's operator()
must be callable with an argument of type bslmf::Nil
.apply
and applyRaw
methods returning non-void type the return value of all callable overloads of operator()
must be convertible to this type.The apply
method should be preferred over a switch
statement based on the type index of a variant. If the order or types contained by the variant is changed in the future, every place where the type index is hard-coded needs to be updated. Whereas if apply
were used, no change would be needed because function overloading will automatically resolve to the proper operator()
to invoke.
There are several variations of the apply
method, varying based on the return type and the handling of unset variants. Firstly, the method varies based on whether apply
returns a value or not. There can either be:
apply
.The default is no return value. Even if visitor's operator()
returns any non-void value, it will not be passed to the user. If users would like to return a value from the visitor's operator()
, they can specify a public alias ResultType
to the desired return type in the functor class. For example, if operator()
were to return an int
, the functor class should specify:
If ResultType
cannot be determined, users also have the option of explicitly specifying the return type when invoking apply:
Secondly, the apply
method varies based on how the method handles an unset variant. A user can choose to:
bslmf::Nil
to the visitor.Furthermore, if the user is sure that the variant cannot be unset, the user can invoke applyRaw
, which is slightly more efficient. However, if the variant is, in fact, unset, the behavior of applyRaw
is undefined.
BDEX streaming is not implemented for any of the variant classes.
Due to the complexity of the implementation, the following synopsis is provided to aid users in locating documentation for functions. Note that this is not a complete summary of all available methods; only the key methods are shown. For more information, refer to the function-level documentation.
Create a variant. Users can choose to initialize a variant with a specified value, or leave the variant in the unset state (via default construction). Users can also supply a bslma::Allocator *
for memory allocation.
Assign a different value of template parameter TYPE
to the variant.
Assign another variant to a variant.
Access a variant's value using a specified visitor functor whereby bslmf::Nil
is passed to the visitor's operator()
if the variant is unset.
Access a variant's value using a specified visitor functor whereby a user-specified default value is passed to the visitor's operator()
if the variant is unset.
Access a variant's value using a specified visitor functor whereby the behavior is undefined if the variant is unset.
Create a new value of template parameter TYPE
in-place, with up to 14 constructor arguments.
Reset a variant to the unset state.
Access the value of template parameter TYPE
currently held by a variant. This method should be invoked using the syntax the<TYPE>()
, e.g., the<int>()
.
Check whether a variant is currently holding a particular type. This method should be invoked using the syntax is<TYPE>()
, e.g., is<int>()
.
Return true
if a variant is currently unset, and false
otherwise.
Write a description of a variant to a specified stream
.
This section illustrates intended use of this component.
The following example illustrates the different ways of constructing a bdlb::Variant
:
The contained types can be retrieved as a bslmf::TypeList
(using the TypeList
nested type), or individually (using TypeN
, for N
varying from 1 to the length of the TypeList
). In the example below, we use the List
variant, but this could be substituted with List3
with no change to the code:
We can check that the variant defaults to the unset state by using the is<TYPE>
and typeIndex
methods:
Single-argument construction from a type in the TypeList
of a variant is also supported. This is more efficient than creating an unset variant and assigning a value to it:
Furthermore, createInPlace
is provided to support direct in-place construction. This method allows users to directly construct the target type inside the variant, instead of first creating a temporary object, then copy constructing the object to initialize the variant:
Up to 14 constructor arguments are supported for in-place construction of an object. Users can also safely create another object of the same or different type in a variant that already holds a value using the createInPlace
method. No memory is leaked in all cases and the destructor for the currently held object is invoked:
createInPlace
returns a reference providing modifiable access to the created object:
A value of a given type can be stored in a variant in three different ways:
operator=
assignTo<TYPE>
assign
operator=
automatically deduces the type that the user is trying to assign to the variant. This should be used most of the time. The assignTo<TYPE>
method should be used when conversion to the type that the user is assigning to is necessary (see the first two examples below for more details). Finally, assign
is equivalent to operator=
and exists simply for backwards compatibility.
The following example illustrates how to use operator=
:
Note that the type of the object is deduced automatically during assignment, as in:
This automatic deduction, however, cannot be extended to conversion constructors, such as:
The compiler will diagnose that const char *
is not a variant type specified in the list of parameter types used in the definition of List
, and will trigger a compile-time assertion. To overcome this problem, see the next usage example of assignTo<TYPE>
.
In the previous example, const char *
was not part of the variant's type list, which resulted in a compilation diagnostic. The use of assignTo<TYPE>
explicitly informs the compiler of the intended type to assign to the variant:
Finally, for backwards compatibility, assign
can also be used in place of operator=
(but not assignTo
):
As described in {Visitors} (above), there are different ways to invoke the apply
method. The first two examples below illustrate the different ways to invoke apply
(with no return value) to control the behavior of visiting an unset variant:
bslmf::Nil
is passed to the visitor.A third example illustrates use of applyRaw
, the behavior of which is undefined if the variant is unset. Two final examples illustrate different ways to specify the return value from apply
:
A simple visitor that does not require any return value might be one that prints the value of the variant to stdout
:
The above prints the following on stdout
:
Note that operator()
is overloaded with bslmf::Nil
. A direct match has higher precedence than a template parameter match. When the variant is unset (such as x[3]
), a bslmf::Nil
is passed to the visitor.
Instead of using bslmf::Nil
, users can also specify a default value to pass to the visitor when the variant is currently unset. Using the same my_PrintVisitor
class from the previous example:
Now, the above code prints the following on stdout
:
This variation of apply
is useful since the user can provide a default value to the visitor without incurring the cost of initializing the variant itself.
If it is certain that a variant is not unset, then the applyRaw
method can be used instead of apply
. applyRaw
is slightly more efficient than apply
, but the behavior of applyRaw
is undefined if the variant is unset. In the following application of applyRaw
, we purposely circumvent x[3]
from being visited because we know that it is unset:
Users can also specify a return type that operator()
will return by specifying a typedef
with the name ResultType
in their functor class. This is necessary in order for the apply
method to know what type to return at compile time:
The above prints the following on stdout
:
Note that if no typedef
is provided (as in the my_PrintVisitor
class), then the default return value is void
.
There may be some cases when a visitor interface is not owned by a client (hence the client cannot add a typedef
to the visitor), or the visitor could not determine the return type at design time. In these scenarios, users can explicitly specify the return type when invoking apply
: