Provide a variant (discriminated union
-like) type.
More...
Namespaces |
namespace | bdlb |
Detailed Description
- Outline
-
-
- Purpose:
- Provide a variant (discriminated
union
-like) type.
-
- Classes:
-
- See also:
-
- Description:
- 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: bdlb::Variant<typename TYPELIST::Type1, typename TYPELIST::Type2, ...>
- Lastly, move constructors (taking an optional allocator) and move-assignment operators are also provided. Note that move semantics are emulated with C++03 compilers.
-
- Default Construction:
- 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.
-
- Visitors:
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:
-
The visitor's
operator()
must be callable with any of the types that might be contained in the variant.
-
For the
apply
methods (but not applyRaw
) the visitor's operator()
must be callable with an argument of type bslmf::Nil
.
-
For the
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:
-
No return value.
-
A return type specified in the visitor interface.
-
A return type specified explicitly when invoking
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:
-
Pass a default-constructed
bslmf::Nil
to the visitor.
-
Pass a user-specified "default" value 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 Streamability:
- BDEX streaming is not implemented for any of the variant classes.
-
- Class Synopsis:
- 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.
-
- Creators:
bdlb::Variant()
bdlb::Variant(const TYPE_OR_ALLOCATOR& valueOrAllocator);
bdlb::Variant(const TYPE& value, bslma::Allocator *basicAllocator);
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.
-
- Manipulators:
- Assign a different value of template parameter
TYPE
to the variant. Assign another variant to a variant. void apply(VISITOR& visitor);
VISITOR::ResultType apply(VISITOR& visitor);
RET_TYPE apply(VISITOR& visitor);
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. void apply(VISITOR& visitor, const TYPE& defaultValue);
VISITOR::ResultType apply(VISITOR& visitor, const TYPE& defaultValue);
RET_TYPE apply(VISITOR& visitor, const TYPE& defaultValue);
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. void applyRaw(VISITOR& visitor);
VISITOR::ResultType applyRaw(VISITOR& visitor);
RET_TYPE applyRaw(VISITOR& visitor);
Access a variant's value using a specified visitor functor whereby the behavior is undefined if the variant is unset. template <class TYPE>
TYPE& createInPlace();
TYPE& createInPlace(const A1& a1);
TYPE& createInPlace(const A1& a1, const A2& a2, ..., const A14& a14);
Create a new value of template parameter TYPE
in-place, with up to 14 constructor arguments. Reset a variant to the unset state. template <class TYPE>
TYPE& the();
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>()
.
-
- Accessors:
template <class TYPE>
bool is() const;
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. bsl::ostream& print(bsl::ostream& stream,
int level = 0,
int spacesPerLevel = 4) const;
Write a description of a variant to a specified stream
.
-
- Usage:
- This section illustrates intended use of this component.
-
- Example 1: Variant Construction:
- 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: assert(3 == List::TypeList::LENGTH);
assert(3 == List3::TypeList::LENGTH);
We can check that the variant defaults to the unset state by using the is<TYPE>
and typeIndex
methods: List x;
assert(!x.is<int>());
assert(!x.is<double>());
assert(!x.is<bsl::string>());
assert(0 == x.typeIndex());
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: List3 y(bsl::string("Hello"));
assert(!y.is<int>());
assert(!y.is<double>());
assert( y.is<bsl::string>());
assert("Hello" == y.the<bsl::string>());
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: List z;
z.createInPlace<bsl::string>("Hello", 5);
assert(!z.is<int>());
assert(!z.is<double>());
assert( z.is<bsl::string>());
assert("Hello" == z.the<bsl::string>());
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: z.createInPlace<bsl::string>("Hello", 5);
assert(z.is<bsl::string>());
assert("Hello" == z.the<bsl::string>());
z.createInPlace<double>(10.0);
assert(z.is<double>());
assert(10.0 == z.the<double>());
z.createInPlace<int>(10);
assert(z.is<int>());
assert(10 == z.the<int>());
createInPlace
returns a reference providing modifiable access to the created object: bsl::string& ref = z.createInPlace<bsl::string>("Goodbye");
assert("Goodbye" == z.the<bsl::string>());
assert("Goodbye" == ref);
assert(&ref == &z.the<bsl::string>());
ref = "Hello again!";
assert("Hello again!" == z.the<bsl::string>());
-
- Example 2: Variant Assignment:
- 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.
-
- operator=:
- The following example illustrates how to use
operator=
: typedef bdlb::Variant<int, double, bsl::string> List;
List x;
List::Type1 v1 = 1;
List::Type2 v2 = 2.0;
List::Type3 v3("hello");
x = v1;
assert( x.is<int>());
assert(!x.is<double>());
assert(!x.is<bsl::string>());
assert(v1 == x.the<int>());
x = v2;
assert(!x.is<int>());
assert( x.is<double>());
assert(!x.is<bsl::string>());
assert(v2 == x.the<double>());
x = v3;
assert(!x.is<int>());
assert(!x.is<double>());
assert( x.is<bsl::string>());
assert(v3 == x.the<bsl::string>());
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: x = static_cast<const char *>("Bye");
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>
.
-
- 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: x.assignTo<bsl::string>(static_cast<const char *>("Bye"));
assert(!x.is<int>());
assert(!x.is<double>());
assert( x.is<bsl::string>());
assert("Bye" == x.the<bsl::string>());
-
- assign:
- Finally, for backwards compatibility,
assign
can also be used in place of operator=
(but not assignTo
): x.assign<int>(v1);
assert( x.is<int>());
assert(!x.is<double>());
assert(!x.is<bsl::string>());
assert(v1 == x.the<int>());
x.assign<double>(v2);
assert(!x.is<int>());
assert( x.is<double>());
assert(!x.is<bsl::string>());
assert(v2 == x.the<double>());
x.assign<bsl::string>(v3);
assert(!x.is<int>());
assert(!x.is<double>());
assert( x.is<bsl::string>());
assert(v3 == x.the<bsl::string>());
-
- Example 3: Visiting a Variant via apply:
- 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 user-specified default value 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
:
-
The return value is specified in the visitor.
-
The return value is specified with the function call.
-
- bslmf::Nil Passed to Visitor:
- A simple visitor that does not require any return value might be one that prints the value of the variant to
stdout
: class my_PrintVisitor {
public:
template <class TYPE>
void operator()(const TYPE& value) const
{
bsl::cout << value << bsl::endl;
}
void operator()(bslmf::Nil value) const
{
bsl::cout << "null" << bsl::endl;
}
};
typedef bdlb::Variant<int, double, bsl::string> List;
List x[4];
x[0].assign(1);
x[1].assign(1.1);
x[2].assignTo<bsl::string>(static_cast<const char *>("Hello"));
my_PrintVisitor printVisitor;
for (int i = 0; i < 4; ++i) {
x[i].apply(printVisitor);
}
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.
-
- User-Specified Default Value Passed to 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: for (int i = 0; i < 4; ++i) {
x[i].apply(printVisitor, "Print this when unset");
}
Now, the above code prints the following on stdout
: 1
1.1
Hello
Print this when unset
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.
-
- applyRaw Undefined If Variant Is Unset:
- 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: for (int i = 0; i < 3; ++i) {
assert(!x[i].isUnset());
x[i].applyRaw(printVisitor);
}
assert(x[3].isUnset());
-
- Return Value Specified in Visitor:
- 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: class my_AddVisitor {
public:
typedef bool ResultType;
template <class TYPE>
ResultType operator()(TYPE& value) const
{
if (bsl::is_convertible<TYPE, double>::value) {
return true;
}
return false;
}
};
typedef bdlb::Variant<int, double, bsl::string> List;
List x[3];
x[0].assign(1);
x[1].assign(1.1);
x[2].assignTo<bsl::string>(static_cast<const char *>("Hello"));
my_AddVisitor addVisitor;
bool ret[3];
for (int i = 0; i < 3; ++i) {
ret[i] = x[i].apply(addVisitor);
if (!ret[i]) {
bsl::cout << "Cannot add to types not convertible to 'double'."
<< bsl::endl;
}
}
assert(true == ret[0]);
assert(true == ret[1]);
assert(false == ret[2]);
The above prints the following on stdout
: Cannot add to types not convertible to 'double'.
Note that if no typedef
is provided (as in the my_PrintVisitor
class), then the default return value is void
.
-
- Return Value Specified with Function Call:
- 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
: class ThirdPartyVisitor {
public:
template <class TYPE>
bsl::string operator()(const TYPE& value) const;
};
typedef bdlb::Variant<int, double, bsl::string> List;
List x[3];
x[0].assign(1);
x[1].assign(1.1);
x[2].assignTo<bsl::string>(static_cast<const char *>("Hello"));
ThirdPartyVisitor visitor;
for (int i = 0; i < 3; ++i) {
bsl::string ret = x[i].apply<bsl::string>(visitor);
bsl::cout << ret << bsl::endl;
}