//// Copyright 2018, 2019 Peter Dimov 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 //// [#overview] # Overview :idprefix: overview_ ## Description This library implements a type-safe discriminated/tagged union type, `variant`, that is API-compatible with the {cpp}17 Standard's http://en.cppreference.com/w/cpp/utility/variant[`std::variant`]. A `variant` variable can hold a value of any of the types `T1`, `T2`, ..., `Tn`. For example, `variant` can hold an `int64_t` value, a `double` value, or a `string` value. Such a type is sometimes called a "tagged union", because it's roughly equivalent to ``` struct V { enum tag { tag_int64_t, tag_double, tag_string }; tag tag_; union { int64_t i_; double d_; std::string s_; }; }; ``` ## Usage Examples Variants can be used to represent dynamically-typed values. A configuration file of the form ``` server.host=test.example.com server.port=9174 cache.max_load=0.7 ``` can be represented as `std::map>`. Variants can also represent polymorphism. To take a classic example, a polymorphic collection of shapes: ``` #define _USE_MATH_DEFINES #include #include #include #include class Shape { public: virtual ~Shape() = default; virtual double area() const = 0; }; class Rectangle: public Shape { private: double width_, height_; public: Rectangle( double width, double height ): width_( width ), height_( height ) {} virtual double area() const { return width_ * height_; } }; class Circle: public Shape { private: double radius_; public: explicit Circle( double radius ): radius_( radius ) {} virtual double area() const { return M_PI * radius_ * radius_; } }; double total_area( std::vector> const & v ) { double s = 0.0; for( auto const& p: v ) { s += p->area(); } return s; } int main() { std::vector> v; v.push_back( std::unique_ptr( new Circle( 1.0 ) ) ); v.push_back( std::unique_ptr( new Rectangle( 2.0, 3.0 ) ) ); std::cout << "Total area: " << total_area( v ) << std::endl; } ``` can instead be represented as a collection of `variant` values. This requires the possible `Shape` types be known in advance, as is often the case. In return, we no longer need virtual functions, or to allocate the values on the heap with `new Rectangle` and `new Circle`: ``` #define _USE_MATH_DEFINES #include #include #include #include using namespace boost::variant2; struct Rectangle { double width_, height_; double area() const { return width_ * height_; } }; struct Circle { double radius_; double area() const { return M_PI * radius_ * radius_; } }; double total_area( std::vector> const & v ) { double s = 0.0; for( auto const& x: v ) { s += visit( []( auto const& y ){ return y.area(); }, x ); } return s; } int main() { std::vector> v; v.push_back( Circle{ 1.0 } ); v.push_back( Rectangle{ 2.0, 3.0 } ); std::cout << "Total area: " << total_area( v ) << std::endl; } ``` ## Construction and Assignment If we look at the ``` v.push_back( Circle{ 1.0 } ); ``` line, we can deduce that `variant` can be (implicitly) constructed from `Circle` (and `Rectangle`), and indeed it can. It can also be assigned a `Circle` or a `Rectangle`: ``` variant v = Circle{ 1.0 }; // v holds Circle v = Rectangle{ 2.0, 3.0 }; // v now holds Rectangle ``` If we try to construct `variant` from something that is neither `int` nor `float`, say, `(short)1`, the behavior is "as if" the `variant` has declared two constructors, ``` variant::variant(int x); variant::variant(float x); ``` and the standard overload resolution rules are used to pick the one that will be used. So `variant((short)1)` will hold an `int`. ## Inspecting the Value Putting values into a `variant` is easy, but taking them out is necessarily a bit more convoluted. It's not possible for `variant` to define a member function `get() const`, because such a function will need its return type fixed at compile time, and whether the correct return type is `int` or `float` will only become known at run time. There are a few ways around that. First, there is the accessor member function ``` std::size_t variant::index() const noexcept; ``` that returns the zero-based index of the current type. For `variant`, it will return `0` for `int` and `1` for `float`. Once we have the index, we can use the free function `get` to obtain the value. Since we're passing the type index to `get`, it knows what to return. `get<0>(v)` will return `int`, and `get<1>(v)` will return `float`: ``` void f( variant const& v ) { switch( v.index() ) { case 0: // use get<0>(v) break; case 1: // use get<1>(v) break; default: assert(false); // never happens } } ``` If we call `get<0>(v)`, and `v.index()` is not currently `0`, an exception (of type `bad_variant_access`) will be thrown. An alternative approach is to use `get(v)` or `get(v)`. This works similarly. Another alternative that avoids the possibility of `bad_variant_access` is to use `get_if`. Instead of a reference to the contained value, it returns a pointer to it, returning `nullptr` to indicate type mismatch. `get_if` takes a pointer to the `variant`, so in our example we'll use something along the following lines: ``` void f( variant const& v ) { if( int const * p = get_if(&v) ) { // use *p } else if( float const * p = get_if(&v) ) { // use *p } else { assert(false); // never happens } } ``` ## Visitation Last but not least, there's `visit`. `visit(f, v)` calls the a function object `f` with the value contained in the `variant` `v` and returns the result. When `v` is `variant`, it will call `f` with either an `int` or a `float`. The function object must be prepared to accept both. In practice, this can be achieved by having the function take a type that can be passed either `int` or `float`, such as `double`: ``` double f( double x ) { return x; } double g( variant const& v ) { return visit( f, v ); } ``` By using a function object with an overloaded `operator()`: ``` struct F { void operator()(int x) const { /* use x */ } void operator()(float x) const { /* use x */ } }; void g( variant const& v ) { visit( F(), v ); } ``` Or by using a polymorphic lambda, as we did in our `Circle`/`Rectangle` example: ``` void g( variant const& v ) { visit( [&]( auto const& x ){ std::cout << x << std::endl; }, v ); } ``` `visit` can also take more than one `variant`. `visit(f, v1, v2)` calls `f(x1, x2)`, where `x1` is the value contained in `v1` and `x2` is the value in `v2`. ## Default Construction The default constructor of `variant` value-initializes the first type in the list. `variant{}` holds `0` (of type `int`), and `variant{}` holds `0.0f`. This is usually the desired behavior. However, in cases such as `variant`, one might legitimately wish to avoid constructing a `std::mutex` by default. A provided type, `monostate`, can be used as the first type in those scenarios. `variant` will default-construct a `monostate`, which is basically a no-op, as `monostate` is effectively an empty `struct`.