std::variant's: Intelligent C++ Unions (C++17)

(Heavily from https://www.cppstories.com/2018/06/variant/)

Syntax

constexpr variant() noexcept(/* see below */); // (1)
constexpr variant( const variant& other ); // (2)
constexpr variant( variant&& other ) noexcept(/* see below */); // (3)
template< class T > // (4)
constexpr variant( T&& t ) noexcept(/* see below */);
template< class T, // (5)
          class... Args >
constexpr explicit variant( std::in_place_type_t<T>,
                            Args&&... args );

template< class T, // (6)
          class U,
          class... Args >
constexpr explicit variant( std::in_place_type_t<T>,
                            std::initializer_list<U> il,
                            Args&&... args );

template< std::size_t I, // (7)
          class... Args >
constexpr explicit variant( std::in_place_index_t<I>,
                            Args&&... args );

template< std::size_t I, // (8)
          class U,
          class... Args >
constexpr explicit variant( std::in_place_index_t<I>,
                            std::initializer_list<U> il,
                            Args&&... args );

The Currently Used Typed

We learn the currently used type via my_variant.index(), which returns an integer, or via std::holds_alternative<aType>(my_variant).

Getting the Value

You can use either std::get<its_type>(my_variant) or std::get<its_index>(my_variant).

If you write the wrong index or the wrong type, a bad_variant_access exception is thrown.

std::visit(visitor-function-object, element)

An example:

#include <iostream>
#include <variant>
#include <vector>

struct NodeA {};
struct NodeB {};
struct NodeC {};

using Nodes = std::variant<NodeA, NodeB, NodeC>;

struct Visitor {
    void operator() (const NodeA&) { std::cout << "NodeA" << std::endl; }
    void operator() (const NodeB&) { std::cout << "NodeB" << std::endl; }
    void operator() (const NodeC&) { std::cout << "NodeC" << std::endl; }
};

int main() {
    std::vector<Nodes> nodes = {
        NodeA{},
        NodeB{},
        NodeA{},
        NodeC{}
    };

    for (auto&& node : nodes)
      std::visit(Visitor{}, node);

    return 0;
}

Output:

NodeA
NodeB
NodeA
NodeC

As you can see, all you need is a callable struct with overloads for required types. Note that you need to overload on all types that belong to Nodes or you’ll get a compiler error (like with pattern matching in other languages).