forked from boostorg/variant2
Update overview
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
////
|
||||
Copyright 2018 Peter Dimov
|
||||
Copyright 2018, 2019 Peter Dimov
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
|
||||
@@ -11,30 +11,313 @@ http://www.boost.org/LICENSE_1_0.txt
|
||||
# Overview
|
||||
:idprefix:
|
||||
|
||||
This library implements a type-safe discriminated union (variant) type,
|
||||
`variant<T...>`, that almost conforms to the {cpp}17 Standard's
|
||||
http://en.cppreference.com/w/cpp/utility/variant[`std::variant<T...>`]. The
|
||||
main differences between the two are:
|
||||
This library implements a type-safe discriminated/tagged union type,
|
||||
`variant<T...>`, that is API-compatible with the {cpp}17 Standard's
|
||||
http://en.cppreference.com/w/cpp/utility/variant[`std::variant<T...>`].
|
||||
|
||||
* `variant<T...>` does not have a valueless-by-exception state;
|
||||
* A converting constructor from, e.g. `variant<int, float>` to
|
||||
`variant<float, double, int>` is provided as an extension;
|
||||
* The reverse operation, going from `variant<float, double, int>` to
|
||||
`variant<int, float>` is provided as the member function `subset<U...>`.
|
||||
(This operation can throw if the current state of the variant cannot be
|
||||
represented.)
|
||||
* `variant<T...>` is not trivial when all contained types are trivial, as
|
||||
mandated by {cpp}17.
|
||||
A `variant<T1, T2, ..., Tn>` variable can hold a value of any of the
|
||||
types `T1`, `T2`, ..., `Tn`. For example,
|
||||
`variant<int64_t, double, std::string>` can hold an `int64_t` value, a
|
||||
`double` value, or a `string` value.
|
||||
|
||||
To avoid the valueless-by-exception state, this implementation falls
|
||||
back to using double storage unless
|
||||
Such a type is sometimes called a "tagged union", because it's roughly
|
||||
equivalent to
|
||||
|
||||
* one of the alternatives is the type `monostate`,
|
||||
* one of the alternatives has a nonthrowing default constructor, or
|
||||
* all the contained types are nothrow move constructible.
|
||||
```
|
||||
struct V
|
||||
{
|
||||
enum tag { tag_int64_t, tag_double, tag_string };
|
||||
|
||||
If the first two bullets don't hold, but the third does, the variant uses
|
||||
single storage, but `emplace` constructs a temporary and moves it into place
|
||||
if the construction of the object can throw. In case this is undesirable, one
|
||||
can force `emplace` into always constructing in-place by adding `monostate` as
|
||||
one of the alternatives.
|
||||
tag tag_;
|
||||
|
||||
union
|
||||
{
|
||||
int64_t i_;
|
||||
double d_;
|
||||
std::string s_;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
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<std::string, variant<int64_t, double,
|
||||
std::string>>`.
|
||||
|
||||
Variants can also represent polymorphism. To take a classic example, a
|
||||
polymorphic collection of shapes:
|
||||
|
||||
```
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
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<std::unique_ptr<Shape>> const & v )
|
||||
{
|
||||
double s = 0.0;
|
||||
|
||||
for( auto const& p: v )
|
||||
{
|
||||
s += p->area();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::vector<std::unique_ptr<Shape>> v;
|
||||
|
||||
v.push_back( std::unique_ptr<Shape>( new Circle( 1.0 ) ) );
|
||||
v.push_back( std::unique_ptr<Shape>( new Rectangle( 2.0, 3.0 ) ) );
|
||||
|
||||
std::cout << "Total area: " << total_area( v ) << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
can instead be represented as a collection of `variant<Rectangle, Circle>`
|
||||
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 <iostream>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include <boost/variant2/variant.hpp>
|
||||
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<variant<Rectangle, Circle>> 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<variant<Rectangle, Circle>> v;
|
||||
|
||||
v.push_back( Circle{ 1.0 } );
|
||||
v.push_back( Rectangle{ 2.0, 3.0 } );
|
||||
|
||||
std::cout << "Total area: " << total_area( v ) << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
If we look at the
|
||||
|
||||
```
|
||||
v.push_back( Circle{ 1.0 } );
|
||||
```
|
||||
|
||||
line, we can deduce that `variant<Rectangle, Circle>` can be (implicitly)
|
||||
constructed from `Circle` (and `Rectangle`), and indeed it can. It can also
|
||||
be assigned a `Circle` or a `Rectangle`:
|
||||
|
||||
```
|
||||
variant<Rectangle, Circle> v = Circle{ 1.0 }; // v holds Circle
|
||||
v = Rectangle{ 2.0, 3.0 }; // v now holds Rectangle
|
||||
```
|
||||
|
||||
If we try to construct `variant<int, float>` 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<int, float>((short)1)` will hold an `int`.
|
||||
|
||||
Putting values into a `variant` is easy, but taking them out is necessarily a
|
||||
bit more convoluted. It's not possible for `variant<int, float>` 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<int,
|
||||
float>`, it will return `0` for `int` and `1` for `float`.
|
||||
|
||||
Once we have the index, we can use the free function `get<N>` 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<int, float> 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<int>(v)` or `get<float>(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<int, float> const& v )
|
||||
{
|
||||
if( int const * p = get_if<int>(&v) )
|
||||
{
|
||||
// use *p
|
||||
}
|
||||
else if( float const * p = get_if<float>(&v) )
|
||||
{
|
||||
// use *p
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false); // never happens
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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<int, float>`, 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<int, float> 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<int, float> const& v )
|
||||
{
|
||||
visit( F(), v );
|
||||
}
|
||||
```
|
||||
|
||||
Or by using a polymorphic lambda, as we did in our `Circle`/`Rectangle`
|
||||
example:
|
||||
|
||||
```
|
||||
void g( variant<int, float> 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`.
|
||||
|
||||
The default constructor of `variant` value-initializes the first type in
|
||||
the list. `variant<int, float>{}` holds `0` (of type `int`), and
|
||||
`variant<float, int>{}` holds `0.0f`.
|
||||
|
||||
This is usually the desired behavior. However, in cases such as
|
||||
`variant<std::mutex, std::recursive_mutex>`, 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<monostate,
|
||||
std::mutex, std::recursive_mutex>` will default-construct a `monostate`,
|
||||
which is basically a no-op, as `monostate` is effectively an empty `struct`.
|
||||
|
Reference in New Issue
Block a user