BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdlb_variant

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, ...>
Definition bdlb_variant.h:2312

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 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:

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:

typedef int ResultType;

If ResultType cannot be determined, users also have the option of explicitly specifying the return type when invoking apply:

apply<int>(visitor);

Secondly, the apply method varies based on how the method handles an unset variant. A user can choose to:

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(const TYPE_OR_ALLOCATOR& valueOrAllocator);
bdlb::Variant(const TYPE& value, bslma::Allocator *basicAllocator);
Definition bdlb_algorithmworkaroundutil.h:74
Definition balxml_encoderoptions.h:68

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

bdlb::Variant& operator=(const TYPE& value);

Assign a different value of template parameter TYPE to the variant.

bdlb::Variant& operator=(const bdlb::Variant& rhs);

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.

void reset();

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>().

bool isUnset() const;

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:

typedef bdlb::Variant <int, double, bsl::string> List;
typedef bdlb::Variant3<int, double, bsl::string> List3; // equivalent
Definition bdlb_variant.h:2698

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());
Definition bslstl_string.h:1281

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= 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= {#bdlb_variant-operator=}

The following example illustrates how to use operator=:

List x;
List::Type1 v1 = 1; // 'int'
List::Type2 v2 = 2.0; // 'double'
List::Type3 v3("hello"); // 'bsl::string'
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:

x = v1;

This automatic deduction, however, cannot be extended to conversion constructors, such as:

x = static_cast<const char *>("Bye"); // ERROR

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> {#bdlb_variant-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:

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:

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;
}
};
List x[4];
// Note that 'x[3]' is uninitialized.
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);
}
VariantImp & assign(const TYPE &value)
VariantImp & assignTo(const SOURCE_TYPE &value)
This struct is empty and represents a nil type.
Definition bslmf_nil.h:131

The above prints the following on stdout:

1
1.1
Hello
null

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) { // NOT 'i < 4' as above.
assert(!x[i].isUnset());
x[i].applyRaw(printVisitor); // undefined behavior for 'x[3]'
}
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;
// Note that the return type of 'operator()' is 'ResultType'.
template <class TYPE>
ResultType operator()(TYPE& value) const
// Return 'true' when addition is performed successfully, and
// 'false' otherwise.
{
// Add certain values to the variant. The details are elided
// as it is the return value that is the focus of this example.
return true; // RETURN
}
return false;
}
};
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]);
Definition bslmf_isconvertible.h:867

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;
// Return the name of the specified 'value' as a 'bsl::string'.
// Note that the implementation of this class is deliberately not
// shown since this class belongs to a third-party library.
};
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) {
// Note that the return type is explicitly specified.
bsl::string ret = x[i].apply<bsl::string>(visitor);
bsl::cout << ret << bsl::endl;
}