rvariant is a variant library that supports recursive types. It is API-compatible with std::variant and includes Boost-style interfaces that act as proxies to the standard-compatible API.

// A common pattern for representing recursive ASTs using recursive variants.
struct BinaryExpr;
using Expr = temp_ns::rvariant<int, double, temp_ns::recursive_wrapper<BinaryExpr>>;
enum class Op;
struct BinaryExpr { Expr lhs, rhs; Op op{}; };

Expr expr{BinaryExpr{Expr{42}, Expr{3.14}}};
expr.visit(temp_ns::overloaded{
    [](int const&) { /* ... */ },
    [](double const&) { /* ... */ },
    [](BinaryExpr const&) { /* ... */ },
});
This library is intended for future submission to the Boost Libraries. Until then, the namespace is documented as temp_ns to indicate that the name is not yet stable. The current implementation temporarily uses the namespace yk, which originates from the prototype codebase.

Introduction

Motivation

Since its introduction in 2004, boost::variant has been used for a wide range of type-safe union use cases.

Starting with C++17, most of those can be replaced with std::variant, except for recursive types. As a result, many users of generic frameworks that require recursive variants—​most notably Boost.Spirit—​have continued using boost::variant, despite its significant impact on compile times.

The compile-time slowness of boost::variant stems from long-standing technical debt: it relies heavily on preprocessor magic in Boost.MPL. This wizardry is so tightly coupled with boost::variant's internals that any attempt to modernize it would be unrealistic; it would effectively require a complete rewrite.

Until 2025, no one had managed to introduce a modern alternative into either Boost or the C++ standard. rvariant fills this gap with a new implementation that supports recursive types while remaining API-compatible with std::variant.

Project Goals

  1. Provide a modern, efficient, and battle-tested recursive variant library to enable developers to avoid using Boost.Variant in new applications.

  2. Replace existing usages of Boost.Variant in established frameworks, especially Boost.Spirit.X3.

  3. Explore potential directions for future standardization, while gaining implementation experience with the modernized design.

Comparison of Variant Libraries

rvariant

std::variant

Boost.Variant

Boost.Variant2

Minimum C++ version

C++23

C++17

C++11
Mostly legacy code from 2003

C++11

Compilation speed

Average

Average

Very Slow

Average

Interface for recursive types

Yes

No

Yes

No

May be valueless?

Yes

Yes

No

No

Exception safety

Basic exception safety

Basic exception safety

Strong exception safety
(Temporary heap backup)

Strong exception safety
(Double storage)

Flexible construction
V<A,B> = V<B,A>
V<A,B,C> = V<A,B>

Yes

No

Yes

Yes

Rationale

Why is recursive_wrapper needed?

A recursive type is effectively an incomplete type at the point of its initial definition. However, a std-style variant class requires all alternative types to be complete, since the storage size must be determined at compile time. As a result, recursive alternatives must be wrapped in a recursive wrapper class, which holds the incomplete type via a pointer and manages it through dynamic memory allocation.

Why can’t I just use std::unique_ptr instead of recursive_wrapper?

Theoretically, a recursive wrapper class can be designed independently, i.e. having no correlation to the variant class itself. So there’s no theoretical reason to prevent you from using std::unique_ptr with std::variant.

However, it is essential to have compatible interfaces in the variant class, because the lack of interfaces means that you must:

  • wrap the actual type with the wrapper class every time you modify variants, and

  • unwrap the instance every time you access an alternative with get() or visit().

In other words, the commonly seen advice like "use std::unique_ptr if you want to hold recursive types in std::variant" is a hack that basically does nothing except for just holding the wrapper instance. As said above, such unadapted wrappers introduce intrusive boilerplates that spreads to the application layer, which is undesirable in practice.

Why can’t rvariant be a thin wrapper around std::variant?

rvariant is designed to be a strict superset of std::variant, not just to act as a third-party drop-in replacement.

If rvariant is specified correctly, it has the potential to become the only variant library in the C++ standard capable of handling both recursive and non-recursive types transparently. However, if it is designed merely as a thin wrapper around std::variant (e.g. inheritance or composition), such a specification could never be standardized.

For this purpose, the documentation of rvariant is presented in a diff-based format that mirrors std::variant, with key characteristics marked using underlines.

Why is rvariant being proposed to Boost?

One of the co-authors of rvariant, Nana Sakisaka, has been using Boost.Spirit for over 15 years and was endorsed as its maintainer in May 2025. Modernizing recursive variant support is a fundamental part of upgrading the Boost.Spirit.X3 codebase, and has become a main focus of ongoing maintenance efforts.

Although Boost.Spirit.X3 has a somewhat updated counterpart, it was never designed around a formal specification and still retains legacy implementation details from earlier stages of development.

As such, the initial version of rvariant was developed as an internal replacement within Boost.Spirit, as a sub-component with no external exposure. However, before a PR was even submitted, it became clear that the design had potential value beyond Spirit. This prompted a complete redesign into a standalone superset of std::variant, followed by preparation for formal Boost review.

Why can’t rvariant be submitted directly to the C++ Standards Committee?

Historically, the concept of recursive variants in C++ has been closely tied to Boost.Variant and Boost.Spirit.Qi. It has been investigated, implemented, and used across Boost Libraries and by end users for decades. For this reason, we believe it’s best to first gather feedback from the Boost community.

Moreover, we expect the working group to seek implementation experience, which inevitably involves the current state of Boost.Variant and related components. Submitting rvariant independently would likely result in making the process unnecessarily time-consuming.

Are recursive variants useful in general-purpose code?

Boost.Spirit is just one example of a parser combinator library. In practice, any application that constructs statically typed recursive data structures can benefit from a recursive variant class. Furthermore, with the arrival of C++26 reflection, such data structures are likely to become even more common.

A proper solution to these challenges requires a fully-featured library, not just a value-semantic wrapper that happens to work for minimal use cases. We hope this work can serve as a foundation for broader discussion about recursive variants in Boost and beyond.

Feature Cheat Sheet

This section shows the pseudo-code for all features in this library. You can click on the links to jump to the corresponding reference.

Basic Features

using A = int;
using B = double;
struct C {};

using AB  = temp_ns::rvariant<A, B>;
using BA  = temp_ns::rvariant<B, A>;
using ABC = temp_ns::rvariant<A, B, C>;

// constructor
AB ab{42};

{
    AB local_ab{};
} // destructor

ab.emplace<int>(123);
ab.emplace<0>(123);

// assignment
ab = AB{123};

A& a = temp_ns::get<0>(ab);
A& a = temp_ns::get<A>(ab);
C& c = temp_ns::get<C>(ab);     // throws std::bad_variant_access

A* a = temp_ns::get_if<0>(&ab);
A* a = temp_ns::get_if<A>(&ab);
C* c = temp_ns::get_if<C>(&ab); // nullptr

// compatibility with boost; same effect as get_if
A* a = temp_ns::get<0>(&ab);
A* a = temp_ns::get<A>(&ab);
C* c = temp_ns::get<C>(&ab);    // nullptr

ab == ab;
ab < ab;
ab <=> ab; // requires (std::three_way_comparable<Ts> && ...)

auto visitor = temp_ns::overloaded {
    [](A const& a) {},
    [](B const& b) {},
};

ab.visit(visitor);           // member visit
temp_ns::visit(visitor, ab); // function visit

Advanced Features

{
    // flexible construction
    AB ab{BA{}};   // unordered construction
    ABC abc{AB{}}; // subset construction

    // flexible assignment
    ab = BA{};     // unordered assignment
    abc = AB{};    // subset assignment
}

static_assert(temp_ns::variant_size_v<AB> == 2);
static_assert(std::same_as<temp_ns::variant_alternative_t<0, AB>, A>);
static_assert(temp_ns::holds_alternative<A>(ab));

static_assert(!ab.valueless_by_exception());
static_assert(ab.index() != std::variant_npos);

{
    AB tmp;
    ab.swap(tmp);
}
{
    using std::swap;
    AB tmp;
    swap(ab, tmp); // ADL
}

std::size_t _ = std::hash<AB>{ab}();
std::size_t _ = hash_value(ab); // compatibility with boost

// I/O support
{
    using V = ::temp_ns::rvariant<int, double>;

    // operator<< support
    std::cout << V{42} << '\n';  // prints 42

    // std::formatter support
    std::println("{}", V{42});   // prints 42

    constexpr auto v_fmt = temp_ns::variant_format_for<V>("{:04d}", "{:.1f}");
    std::println("foo{}bar", temp_ns::format_by(v_fmt, V(42)); // prints foo0042bar
    std::println("foo{}bar", temp_ns::format_by(v_fmt, V(3.14)); // prints foo3.1bar
}

Reference [rvariant]

General [rvariant.general]

Header <temp_ns/rvariant.hpp> synopsis [rvariant.syn]

#include <compare>
#include <memory>      // for std::allocator, etc.
#include <type_traits> // for std::add_pointer, etc.
#include <utility>     // for std::in_place_type, etc.
#include <variant>     // for compatibility with std::bad_variant_access, etc.

namespace temp_ns {

// [rvariant.rvariant], class template rvariant
template<class... Ts>
class rvariant;

// [rvariant.recursive], class template recursive_wrapper
template<class T, class Allocator = std::allocator<T>>
class recursive_wrapper;

/* all features commented below defined as per [variant] */
    // variant_size, variant_size_v
    // operator==
    // operator!=
    // operator<
    // operator>
    // operator<=
    // operator>=
    // operator<=>
    // swap

/* not defined; use the std:: versions instead */
    // variant_npos
    // monostate and monostate-related functionalities
    // std::bad_variant_access

// [rvariant.helper], rvariant helper classes
template<std::size_t I, class T> struct variant_alternative; // not defined
template<std::size_t I, class T> struct variant_alternative<I, T const>;
template<std::size_t I, class T>
  using variant_alternative_t = typename variant_alternative<I, T>::type;

template<std::size_t I, class... Ts>
  struct variant_alternative<I, rvariant<Ts...>>;

// [rvariant.get], value access
template<class T, class... Ts>
  constexpr bool holds_alternative(rvariant<Ts...> const&) noexcept;

template<std::size_t I, class... Ts>
  constexpr variant_alternative_t<I, rvariant<Ts...>>&
    get(rvariant<Ts...>&);
template<std::size_t I, class... Ts>
  constexpr variant_alternative_t<I, rvariant<Ts...>>&&
    get(rvariant<Ts...>&&);
template<std::size_t I, class... Ts>
  constexpr variant_alternative_t<I, rvariant<Ts...>> const&
    get(rvariant<Ts...> const&);
template<std::size_t I, class... Ts>
  constexpr variant_alternative_t<I, rvariant<Ts...>> const&&
    get(rvariant<Ts...> const&&);

template<class T, class... Ts> constexpr T&        get(rvariant<Ts...>&);
template<class T, class... Ts> constexpr T&&       get(rvariant<Ts...>&&);
template<class T, class... Ts> constexpr T const&  get(rvariant<Ts...> const&);
template<class T, class... Ts> constexpr T const&& get(rvariant<Ts...> const&&);

template<std::size_t I, class... Ts>
  constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>>>
    get_if(rvariant<Ts...>*) noexcept;
template<std::size_t I, class... Ts>
  constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>> const>
    get_if(rvariant<Ts...> const*) noexcept;
template<std::size_t I, class... Ts>
  constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>>>
    get(rvariant<Ts...>*) noexcept;       // compatibility with boost::get
template<std::size_t I, class... Ts>
  constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>> const>
    get(rvariant<Ts...> const*) noexcept; // compatibility with boost::get

template<class T, class... Ts>
  constexpr std::add_pointer_t<T>
    get_if(rvariant<Ts...>*) noexcept;
template<class T, class... Ts>
  constexpr std::add_pointer_t<T const>
    get_if(rvariant<Ts...> const*) noexcept;
template<class T, class... Ts>
  constexpr std::add_pointer_t<T>
    get(rvariant<Ts...>*) noexcept;       // compatibility with boost::get
template<class T, class... Ts>
  constexpr std::add_pointer_t<T const>
    get(rvariant<Ts...> const*) noexcept; // compatibility with boost::get

// [rvariant.visit], visitation
template<class Visitor, class... Variants>
  constexpr see below visit(Visitor&&, Variants&&...);
template<class R, class Visitor, class... Variants>
  constexpr R visit(Visitor&&, Variants&&...);

// [rvariant.hash], hash support
template<class... Ts>
  /* constexpr */ std::size_t hash_value(rvariant<Ts...> const&);

// [rvariant.hash], hash support
template<class T, class Allocator>
  /* constexpr */ std::size_t hash_value(recursive_wrapper<T, Allocator> const&);

// [rvariant.recursive.helper], recursive_wrapper helper classes
template<class T> struct unwrap_recursive;
template<class T, class Allocator> struct unwrap_recursive<recursive_wrapper<T, Allocator>>;
template<class T> using unwrap_recursive_t = typename unwrap_recursive<T>::type;

// [rvariant.pack], pack manipulation and deduping
template<template<class...> class TT, class A, class B>
  struct compact_alternative;
template<template<class...> class TT, class A, class B>
  using compact_alternative_t = typename compact_alternative<TT, A, B>::type;

} // temp_ns
namespace std {

// [rvariant.hash], hash support
template<class... Ts> struct hash<::temp_ns::rvariant<Ts...>>;

// [rvariant.hash], hash support
template<class T, class Allocator> struct hash<::temp_ns::recursive_wrapper<T, Allocator>>;

} // std

Class template rvariant [rvariant.rvariant]

namespace temp_ns {

template<class... Ts>
class rvariant
{
public:
  // [rvariant.ctor], constructors
  constexpr rvariant::rvariant() noexcept(see below);
  constexpr rvariant::rvariant(rvariant const&);
  constexpr rvariant::rvariant(rvariant&&) noexcept(see below);

  template<class T>
    constexpr rvariant(T&&) noexcept(see below);

  template<class T, class... Args>
    constexpr explicit rvariant(std::in_place_type_t<T>, Args&&...);
  template<class T, class U, class... Args>
    constexpr explicit rvariant(std::in_place_type_t<T>, std::initializer_list<U>, Args&&...);
  template<std::size_t I, class... Args>
    constexpr explicit rvariant(std::in_place_index_t<I>, Args&&...);
  template<std::size_t I, class U, class... Args>
    constexpr explicit rvariant(std::in_place_index_t<I>, std::initializer_list<U>, Args&&...);

  // [rvariant.ctor], flexible constructors
  template<class... Us>
    constexpr rvariant(rvariant<Us...> const&);
  template<class... Us>
    constexpr rvariant(rvariant<Us...>&&) noexcept(see below);

  // [rvariant.dtor], destructor
  constexpr ~rvariant();

  // [rvariant.assign], assignment
  constexpr rvariant& operator=(rvariant const&);
  constexpr rvariant& operator=(rvariant&&) noexcept(see below);

  template<class T> constexpr rvariant& operator=(T&&) noexcept(see below);

  // [rvariant.assign], flexible assignment
  template<class... Us>
    constexpr rvariant& operator=(rvariant<Us...> const&);
  template<class... Us>
    constexpr rvariant& operator=(rvariant<Us...>&&) noexcept(see below);

  // [rvariant.mod], modifiers
  template<class T, class... Args>
    constexpr T& emplace(Args&&...);
  template<class T, class U, class... Args>
    constexpr T& emplace(std::initializer_list<U>, Args&&...);
  template<std::size_t I, class... Args>
    constexpr variant_alternative_t<I, rvariant<Ts...>>& emplace(Args&&...);
  template<std::size_t I, class U, class... Args>
    constexpr variant_alternative_t<I, rvariant<Ts...>>&
      emplace(std::initializer_list<U>, Args&&...);

  // [rvariant.status], value status
  constexpr bool valueless_by_exception() const noexcept;
  constexpr std::size_t index() const noexcept;

  // [rvariant.subset], subset
  template<class... Us>
    constexpr rvariant<Us...> subset() const& noexcept(see below);
  template<class... Us>
    constexpr rvariant<Us...> subset() && noexcept(see below);

  // [rvariant.swap], swap
  constexpr void swap(rvariant&) noexcept(see below);

  // [rvariant.visit], visitation
  template<class Self, class Visitor>
    constexpr decltype(auto) visit(this Self&&, Visitor&&);
  template<class R, class Self, class Visitor>
    constexpr R visit(this Self&&, Visitor&&);
};

} // temp_ns

General [rvariant.rvariant.general]

See also: spec of std::variant and boost::variant
  • Class template rvariant follows all requirements of std::variant, unless otherwise noted.

  • All types in Ts must satisfy all requirements on the corresponding parameter in std::variant, unless otherwise noted.

  • Let T and A denote arbitrary types. For the template parameter of rvariant, if a user provides both T and recursive_wrapper<T, A> , the program is ill-formed.

  • Let T denote an arbitrary type. For the template parameter of rvariant, if a user provides multiple different specializations of recursive_wrapper such that the first template parameter is T, the program is ill-formed.

Although rvariant is normally capable of holding duplicate alternatives, the above restriction exists for preventing error-prone instantiation of rvariant:

rvariant<
  int, recursive_wrapper<int>, recursive_wrapper<int, MyAllocator<int>>
> v(42); // error-prone; not allowed
  • Let VTi denote recursive_wrapper<Ti, A> (for any type A) if such a specialization occurs anywhere in Ts...; otherwise, let VTi denote Ti. Let Uj denote the jth type of the template parameter pack having the name Us on each flexibility-related functions. The corresponding alternative for rvariant is the first type for which std::is_same_v<unwrap_recursive_t<VTi>, unwrap_recursive_t<Uj>> is true.

Constructors [rvariant.ctor]

constexpr rvariant::rvariant() noexcept(see below);// 1
constexpr rvariant::rvariant(rvariant const& w);// 2
constexpr rvariant::rvariant(rvariant&& w) noexcept(see below);// 3

// Generic constructor
template<class T>
constexpr rvariant(T&& t) noexcept(see below);// 4


template<class T, class... Args>
constexpr explicit rvariant(std::in_place_type_t<T>, Args&&... args);// 5

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

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

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


// Flexible copy constructor
template<class... Us>
constexpr rvariant(rvariant<Us...> const& w);// 9

// Flexible move constructor
template<class... Us>
constexpr rvariant(rvariant<Us...>&& w) noexcept(see below);// 10
  • 1-3) Equivalent to the std::variant counterpart. [spec]

  • 4) Generic constructor. Equivalent to the std::variant counterpart, [spec] except:

    Postconditions: holds_alternative<unwrap_recursive_t<Tj>>(*this) is true.

  • 5) Mandates: T is not a specialization of recursive_wrapper.

    Let VT denote recursive_wrapper<T, A> (for any type A) if such a specialization occurs anywhere in Ts...; otherwise, let VT denote T.

    Constraints:

    •  — There is exactly one occurrence of T in unwrap_recursive_t<Ts>... and

    •  — std::is_constructible_v<VT, Args...> is true.

    Effects: Direct-non-list-initializes the contained value of type VT with std::forward<Args>(args)....

    Postconditions: holds_alternative<T>(*this) is true.

    Throws: Any exception thrown by calling the selected constructor of VT.

    Remarks: If VT's selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

  • 6) Mandates: T is not a specialization of recursive_wrapper.

    Let VT denote recursive_wrapper<T, A> (for any type A) if such a specialization occurs anywhere in Ts...; otherwise, let VT denote T.

    Constraints:

    •  — There is exactly one occurrence of T in unwrap_recursive_t<Ts>... and

    •  — std::is_constructible_v<VT, std::initializer_list<U>&, Args...> is true.

    Effects: Direct-non-list-initializes the contained value of type VT with il, std::forward<Args>(args)....

    Postconditions: holds_alternative<T>(*this) is true.

    Throws: Any exception thrown by calling the selected constructor of VT.

    Remarks: If VT's selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

  • 7-8) Equivalent to the std::variant counterpart. [spec]

  • 9) Flexible copy constructor.

    Let VTi and Uj denote the types defined in [rvariant.rvariant.general].

    Constraints:

    •  — std::is_same_v<rvariant<Us...>, rvariant> is false, and

    •  — rvariant_set::subset_of<rvariant<Us...>, rvariant> is true, and

    •  — std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...> is false, and

    •  — std::is_constructible_v<VTi, Uj const&> is true for all j.

    Effects: If w holds a value, initializes the rvariant to hold VTi (with i being the index of the alternative corresponding to that of w) and direct-initializes the contained value with GET<w.index()>(w). Otherwise, initializes the rvariant to not hold a value.

    Throws: Any exception thrown by direct-initializing any alternative corresponding to that of w.

    Remarks:

    •  — The exception specification is equivalent to the logical AND of std::is_nothrow_constructible_v<VTi, Uj const&> for all j.

  • 10) Flexible move constructor.

    Let VTi and Uj denote the types defined in [rvariant.rvariant.general].

    Constraints:

    •  — std::is_same_v<rvariant<Us...>, rvariant> is false, and

    •  — rvariant_set::subset_of<rvariant<Us...>, rvariant> is true, and

    •  — std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...> is false, and

    •  — std::is_constructible_v<VTi, Uj&&> is true for all j.

    Effects: If w holds a value, initializes the rvariant to hold VTi (with i being the index of the alternative corresponding to that of w) and direct-initializes the contained value with GET<w.index()>(std::move(w)). Otherwise, initializes the rvariant to not hold a value.

    Throws: Any exception thrown by move-constructing any alternative corresponding to that of w.

    Remarks:

    •  — The exception specification is equivalent to the logical AND of std::is_nothrow_constructible_v<VTi, Uj&&> for all j.

Destructor [rvariant.dtor]

constexpr ~rvariant();// 1
  • 1) Equivalent to the std::variant counterpart. [spec]

Assignment [rvariant.assign]

constexpr rvariant& operator=(rvariant const& rhs);// 1
constexpr rvariant& operator=(rvariant&& rhs) noexcept(see below);// 2

// Generic assignment operator
template<class T>
constexpr rvariant& operator=(T&& t) noexcept(see below);// 3

// Flexible copy assignment operator
template<class... Us>
constexpr rvariant& operator=(rvariant<Us...> const& rhs);// 4

// Flexible move assignment operator
template<class... Us>
constexpr rvariant& operator=(rvariant<Us...>&& rhs) noexcept(see below);// 5
  • 1-2) Equivalent to the std::variant counterpart. [spec]

  • 3) Generic assignment operator. Equivalent to the std::variant counterpart, [spec] except:

    Postconditions: holds_alternative<unwrap_recursive_t<Tj>>(*this) is true, with Tj selected by the imaginary function overload resolution described above.

  • 4) Flexible copy assignment operator.

    Let VTi and Uj denote the types defined in [rvariant.rvariant.general].

    Constraints:

    •  — std::is_same_v<rvariant<Us...>, rvariant> is false, and

    •  — rvariant_set::subset_of<rvariant<Us...>, rvariant> is true, and

    •  — std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...> is false, and

    •  — std::is_constructible_v<VTi, Uj const&> && std::is_assignable_v<VTi&, Uj const&> is true for all j.

    Effects: Let j be rhs.index().

    •  — If neither *this nor rhs holds a value, there is no effect.

    •  — Otherwise, if *this holds a value but rhs does not, destroys the value contained in *this and sets *this to not hold a value.

    •  — Otherwise, if rhs holds a value but *this does not, initializes rvariant to hold VTi (with i being the index of the alternative corresponding to that of rhs) and direct-initializes the contained value with GET<j>(rhs).

    •  — Otherwise, if std::is_same_v<unwrap_recursive_t<Ti>, unwrap_recursive_t<Uj>> is true, assigns GET<j>(rhs) to the value contained in *this. (Note: the left hand side is Ti, not VTi. This ensures that the existing storage is reused even for rvariant with duplicate corresponding alternatives; i.e. index() is unchanged.)

    •  — Otherwise, if either std::is_nothrow_constructible_v<VTi, Uj const&> is true or std::is_nothrow_move_constructible_v<VTi> is false, equivalent to emplace<VTi>(GET<j>(rhs)).

    •  — Otherwise, equivalent to emplace<VTi>(VTi(GET<j>(rhs))).

    Postconditions: If rhs.valueless_by_exception() is true, index() == rhs.index(); otherwise, *this holds the alternative corresponding to that of rhs.

    Returns: *this.

    Remarks: The exception specification is equivalent to the logical AND of std::is_nothrow_constructible_v<VTi, Uj const&> && std::is_nothrow_assignable_v<VTi&, Uj const&> for all j.

  • 5) Flexible move assignment operator.

    Let VTi and Uj denote the types defined in [rvariant.rvariant.general].

    Constraints:

    •  — std::is_same_v<rvariant<Us...>, rvariant> is false,

    •  — rvariant_set::subset_of<rvariant<Us...>, rvariant> is true, and

    •  — std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...> is false, and

    •  — std::is_constructible_v<VTi, Uj&&> && std::is_assignable_v<VTi&, Uj&&> is true for all j.

    Effects: Let j be rhs.index().

    •  — If neither *this nor rhs holds a value, there is no effect.

    •  — Otherwise, if *this holds a value but rhs does not, destroys the value contained in *this and sets *this to not hold a value.

    •  — Otherwise, if rhs holds a value but *this does not, initializes rvariant to hold VTi (with i being the index of the alternative corresponding to that of rhs) and direct-initializes the contained value with GET<j>(std::move(rhs)).

    •  — Otherwise, if std::is_same_v<unwrap_recursive_t<Ti>, unwrap_recursive_t<Uj>> is true, assigns GET<j>(std::move(rhs)) to the value contained in *this. (Note: the left hand side is Ti, not VTi. This ensures that the existing storage is reused even for rvariant with duplicate corresponding alternatives; i.e. index() is unchanged.)

    •  — Otherwise, equivalent to emplace<VTi>(GET<j>(std::move(rhs))).

    Returns: *this.

    Remarks: The exception specification is equivalent to the logical AND of std::is_nothrow_constructible_v<VTi, Uj&&> && std::is_nothrow_assignable_v<VTi&, Uj&&> for all j.

Modifiers [rvariant.mod]

template<class T, class... Args>
constexpr T& emplace(Args&&... args);// 1

template<class T, class U, class... Args>
constexpr T& emplace(std::initializer_list<U> il, Args&&... args);// 2

template<std::size_t I, class... Args>
constexpr variant_alternative_t<I, rvariant<Ts...>>&
  emplace(Args&&... args);// 3

template<std::size_t I, class U, class... Args>
constexpr variant_alternative_t<I, rvariant<Ts...>>&
  emplace(std::initializer_list<U> il, Args&&... args);// 4
  • 1) Let VT denote recursive_wrapper<T, A> (for any type A) if such a specialization occurs anywhere in Ts...; otherwise, let VT denote T.

    Mandates: T is not a specialization of recursive_wrapper.

    Constraints: std::is_constructible_v<VT, Args...> is true, and T occurs exactly once in unwrap_recursive_t<Ts>.

    Effects: Equivalent to:
      return emplace<I>(std::forward<Args>(args)...);
    where I is the zero-based index of T in unwrap_recursive_t<Ts>.

  • 2) Let VT denote recursive_wrapper<T, A> (for any type A) if such a specialization occurs anywhere in Ts...; otherwise, let VT denote T.

    Mandates: T is not a specialization of recursive_wrapper.

    Constraints: std::is_constructible_v<VT, std::initializer_list<U>&, Args...> is true, and T occurs exactly once in unwrap_recursive_t<Ts>.

    Effects: Equivalent to:
      return emplace<I>(il, std::forward<Args>(args)...);
    where I is the zero-based index of T in unwrap_recursive_t<Ts>.

  • 3) Equivalent to the std::variant counterpart, [spec] except:

    Returns: Let o denote a reference to the new contained value. Returns UNWRAP_RECURSIVE(o).

    Remarks: If TI is a specialization of recursive_wrapper, this function is permitted to construct an intermediate variable tmp as if by passing std::forward<Args>(args)... to TI's constructor. Then rvariant direct-non-list-initializes the contained value of TI with the argument std::move(tmp). (Note: This allows optimization where rvariant can be assumed to become never valueless on certain cases.)

  • 4) Equivalent to the std::variant counterpart, [spec] except:

    Returns: Let o denote a reference to the new contained value. Returns UNWRAP_RECURSIVE(o).

    Remarks: If TI is a specialization of recursive_wrapper, this function is permitted to construct an intermediate variable tmp as if by passing il, std::forward<Args>(args)... to TI's constructor. Then rvariant direct-non-list-initializes the contained value of TI with the argument std::move(tmp). (Note: This allows optimization where rvariant can be assumed to become never valueless on certain cases.)

Value status [rvariant.status]

constexpr bool valueless_by_exception() const noexcept;
constexpr std::size_t index() const noexcept;

Equivalent to the std::variant counterpart. [spec]

Subset [rvariant.subset]

template<class... Us>
  requires std::is_same_v<rvariant<Us...>, rvariant>
constexpr rvariant subset() const& noexcept(std::is_nothrow_copy_constructible_v<rvariant>);// 1

template<class... Us>
  requires std::is_same_v<rvariant<Us...>, rvariant>
constexpr rvariant subset() && noexcept(std::is_nothrow_move_constructible_v<rvariant>);// 2

template<class... Us>
  requires (!std::is_same_v<rvariant<Us...>, rvariant>)
constexpr rvariant<Us...> subset() const& noexcept(see below);// 3

template<class... Us>
  requires (!std::is_same_v<rvariant<Us...>, rvariant>)
constexpr rvariant<Us...> subset() && noexcept(see below);// 4
  • 1) Returns: *this.

    Throws: Any exception thrown by copy-constructing any type in Us.

  • 2) Returns: std::move(*this).

    Throws: Any exception thrown by move-constructing any type in Us.

  • 3) Mandates: std::is_copy_constructible_v<Uj> is true for all j, where Uj be the jth type in Us.

    Effects: If *this holds a value, returns an rvariant<Us...> object that holds the alternative corresponding to that of *this, with its contained value direct-initialized from GET<i>(*this), where i is this->index(). Otherwise, returns an rvariant<Us...> object that does not hold a value.

    Throws: std::bad_variant_access if *this holds an alternative that is not contained in Us; otherwise, equivalent to the semantics of the flexible copy constructor.

    Remarks:

    •  — This function does not participate in overload resolution unless rvariant_set::subset_of<rvariant<Us...>, rvariant> is true.

    •  — The exception specification is equivalent to the logical AND of rvariant_set::equivalent_to<rvariant<Us...>, rvariant> and std::is_nothrow_constructible_v<rvariant<Us...>, rvariant const&>.

    •  — The corresponding index on the returned rvariant<Us...> object shall be determined according to the rules defined in the flexible copy constructor.

  • 4) Equivalent to the overload #3, except:

    •  — Citation of flexible copy constructor is replaced with flexible move constructor.

    Mandates: std::is_move_constructible_v<Uj> is true for all j, where Uj be the jth type in Us.

    Effects: GET<i>(*this) is replaced with GET<i>(std::move(*this)).

    Remarks: std::is_nothrow_constructible_v<rvariant<Us...>, rvariant const&> is replaced with std::is_nothrow_constructible_v<rvariant<Us...>, rvariant&&>.

Swap [rvariant.swap]

constexpr void swap(rvariant&) noexcept(see below);

Equivalent to the std::variant counterpart. [spec]

rvariant helper classes [rvariant.helper]

namespace temp_ns {

template<std::size_t I, class T>
struct variant_alternative; // not defined

template<std::size_t I, class T>
struct variant_alternative<I, T const>;// 1

template<std::size_t I, class... Ts>
struct variant_alternative<I, rvariant<Ts...>>;// 2

} // temp_ns
  • 1) Equivalent to the std::variant counterpart. [spec]

  • 2) The member typedef type denotes unwrap_recursive_t<TI>.

    Mandates: I < sizeof...(Ts).

Flexibility traits [rvariant.flex]

namespace temp_ns::rvariant_set {

template<class W, class V>
struct is_subset_of : std::false_type {};// 1

template<class... Us, class... Ts>
struct is_subset_of<rvariant<Us...>, rvariant<Ts...>>;// 2

template<class W, class V>
constexpr bool is_subset_of_v = is_subset_of<W, V>::value;

template<class W, class V>
concept subset_of = is_subset_of_v<W, V>;

template<class W, class V>
concept equivalent_to = subset_of<W, V> && subset_of<V, W>;

} // temp_ns::rvariant_set
  • 1) Mandates: Both W and V are specialization of rvariant.

  • 2) Constraints: For every type U in Us, there exists at least one type T in Ts such that:

    •  — T is the same type as U or,

    •  — unwrap_recursive_t<T> is the same type as U.

Value access [rvariant.get]

namespace temp_ns {

template<class T, class... Ts>
constexpr bool holds_alternative(rvariant<Ts...> const& v) noexcept;

} // temp_ns
template<std::size_t I, class... Ts>
constexpr see below& GET(rvariant<Ts...>& v);               // exposition only

template<std::size_t I, class... Ts>
constexpr see below&& GET(rvariant<Ts...>&& v);             // exposition only

template<std::size_t I, class... Ts>
constexpr see below const& GET(rvariant<Ts...> const& v);   // exposition only

template<std::size_t I, class... Ts>
constexpr see below const&& GET(rvariant<Ts...> const&& v); // exposition only
  • Mandates: I < sizeof...(Ts).

    Preconditions: v.index() is I.

    Returns: o, where o denotes a reference to the object stored in v, if the type of the expression’s receiver is a specialization of recursive_wrapper; otherwise, returns UNWRAP_RECURSIVE(o).

namespace temp_ns {

template<std::size_t I, class... Ts>
constexpr variant_alternative_t<I, rvariant<Ts...>>&
  get(rvariant<Ts...>& v);

template<std::size_t I, class... Ts>
constexpr variant_alternative_t<I, rvariant<Ts...>>&&
  get(rvariant<Ts...>&& v);

template<std::size_t I, class... Ts>
constexpr variant_alternative_t<I, rvariant<Ts...>> const&
  get(rvariant<Ts...> const& v);

template<std::size_t I, class... Ts>
constexpr variant_alternative_t<I, rvariant<Ts...>> const&&
  get(rvariant<Ts...> const&& v);

} // temp_ns
  • Mandates: I < sizeof...(Ts).

    Effects: If v.index() is I, returns UNWRAP_RECURSIVE(o), where o denotes a reference to the object stored in the rvariant. Otherwise, throws an exception of type std::bad_variant_access.

namespace temp_ns {

template<class T, class... Ts> constexpr T&        get(rvariant<Ts...>& v);
template<class T, class... Ts> constexpr T&&       get(rvariant<Ts...>&& v);
template<class T, class... Ts> constexpr T const&  get(rvariant<Ts...> const& v);
template<class T, class... Ts> constexpr T const&& get(rvariant<Ts...> const&& v);

} // temp_ns
namespace temp_ns {

template<std::size_t I, class... Ts>
constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>>>
  get_if(rvariant<Ts...>*) noexcept;// 1

template<std::size_t I, class... Ts>
constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>> const>
  get_if(rvariant<Ts...> const* v) noexcept;// 2

template<std::size_t I, class... Ts>
constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>>>
  get(rvariant<Ts...>*) noexcept;         // compatibility with boost::get// 3

template<std::size_t I, class... Ts>
constexpr std::add_pointer_t<variant_alternative_t<I, rvariant<Ts...>> const>
  get(rvariant<Ts...> const* v) noexcept; // compatibility with boost::get// 4

} // temp_ns
  • 1-2) Mandates: I < sizeof...(Ts).

    Returns: A pointer to the value denoted by UNWRAP_RECURSIVE(o), where o denotes a reference to the object stored in the rvariant, if v != nullptr and v->index() == I. Otherwise, returns nullptr.

  • 3-4) Provided for compatibility with boost::get.

    Returns: get_if<I>(v).

namespace temp_ns {

template<class T, class... Ts>
constexpr std::add_pointer_t<T>
  get_if(rvariant<Ts...>* v) noexcept;// 1

template<class T, class... Ts>
constexpr std::add_pointer_t<T const>
  get_if(rvariant<Ts...> const* v) noexcept;// 2

template<class T, class... Ts>
constexpr std::add_pointer_t<T>
  get(rvariant<Ts...>* v) noexcept;       // compatibility with boost::get// 3

template<class T, class... Ts>
constexpr std::add_pointer_t<T const>
  get(rvariant<Ts...> const* v) noexcept; // compatibility with boost::get// 4

} // temp_ns
  • 1-2) Mandates: The type T occurs exactly once in unwrap_recursive_t<Ts>.

    Effects: Equivalent to: return get_if<i>(v); with i being the zero-based index of T in unwrap_recursive_t<Ts>.

    Remarks: This function is defined as deleted if T is a specialization of recursive_wrapper.

  • 3-4) Provided for compatibility with boost::get.

    Returns: get_if<T>(v).

Visitation [rvariant.visit]

namespace temp_ns {

template<class Visitor, class... Variants>
constexpr see below visit(Visitor&& vis, Variants&&... vars);// 1

template<class R, class Visitor, class... Variants>
constexpr R visit(Visitor&& vis, Variants&&... vars);// 2

} // temp_ns

// below are member functions of the class template rvariant:

template<class Self, class Visitor>
constexpr decltype(auto) visit(this Self&& self, Visitor&& vis);// 3

template<class R, class Self, class Visitor>
constexpr R visit(this Self&& self, Visitor&& vis);// 4
  • 1-2) Equivalent to the std::variant counterpart [spec], except that:

    •  — GET<m>(std::forward<V>(vars)) is replaced with UNWRAP_RECURSIVE(GET<m>(std::forward<V>(vars))).

  • 3-4) Equivalent to the std::variant counterpart [spec], except that it forwards to temp_ns::visit instead of std::visit.

Hash support [rvariant.hash]

namespace std {

template<class... Ts>
struct hash<::temp_ns::rvariant<Ts...>>;// 1

template<class T, class Allocator>
struct hash<::temp_ns::recursive_wrapper<T, Allocator>>;// 2

} // std
namespace temp_ns {

template<class... Ts>
/* constexpr */ std::size_t hash_value(rvariant<Ts...> const& v);// 3

template<class T, class Allocator>
/* constexpr */ std::size_t hash_value(recursive_wrapper<T, Allocator> const& rw);// 4

} // temp_ns
  • 1) Equivalent to the std::variant counterpart. [spec]

  • 2) Equivalent to the std::indirect counterpart. [spec]

  • 3) Effects: Equivalent to std::hash<rvariant<Ts...>>{}(v).

  • 4) Effects: Equivalent to std::hash<recursive_wrapper<T, Allocator>>{}(rw).

I/O [rvariant.io]

I/O components are not included by the global convenience header (<temp_ns/rvariant.hpp>).

operator<< support

// <temp_ns/rvariant/rvariant_io.hpp>

#include <ostream>

namespace temp_ns {

template<class T>
constexpr bool ADL-ostreamable = see below; // exposition only// 1

template<class... Ts>
std::ostream& operator<<(std::ostream& os, rvariant<Ts...> const& v);// 2

} // temp_ns
  • 1) Evaluates to true if all of the following conditions are met; otherwise, evaluates to false.

    •  — Let os denote an lvalue reference to an object of type std::ostream, and let val denote an lvalue reference to an object of type T. These references are valid in unevaluated context, and

    •  — the expression os << val is well-formed and has the type std::ostream&, and

    •  — the corresponding overload is found solely via ADL.

  • 2) Constraints: ADL-ostreamable<unwrap_recursive_t<Ti>> is true for all i.

    Effects: Behaves as a formatted output function ([ostream.formatted.reqmts]) of os, except that:

    •  — the output is done as if by calling os << UNWRAP_RECURSIVE(GET<i>(v)) (with i being v.index()), and

    •  — any exception of type std::bad_variant_access, whether thrown directly (i.e. due to v being valueless) or indirectly (i.e. by a nested call to an alternative’s output function), is propagated without regard to the value of os.exceptions() and without turning on std::ios_base::badbit in the error state of os.

    Returns: os.

    Throws: std::bad_variant_access if v.valueless_by_exception() is true. Otherwise, throws any exception thrown as per the formatted output function’s specification.

std::formatter support

  • Let v denote an object of rvariant, and let proxy denote an object of variant_format_proxy.

  • The specialization std::formatter<::temp_ns::rvariant<Ts...>, charT> (for arbitrary charT) is enabled if and only if std::formattable<unwrap_recursive_t<Tsi>, charT> is true for all i, with the following characteristics:

    •  — The format specifier must be empty, otherwise std::format_error is thrown, and

    •  — if v.valueless_by_exception() is true, std::bad_variant_access is thrown, and

    •  — the output is done as if by calling std::format_to(fmt_ctx.out(), paren, UNWRAP_RECURSIVE(GET<v.index()>(v))), with paren being a string literal "{}" interpreted on the target character type.

    • Example:

      std::println("{}", temp_ns::rvariant<int, double>(42)); // prints 42
  • The specialization std::formatter<variant_format_proxy<VFormat, Variant>, charT> is enabled if and only if:

    •  — std::remove_cvref_t<VFormat> is a specialization of variant_format_string, and

    •  — std::remove_cvref_t<Variant> is a specialization of rvariant, and

    •  — std::formattable<unwrap_recursive_t<Tsi>, charT> is true for all i, with Ts being the template parameter pack of cv-unqualified non-reference type for Variant.

  • It has the following characteristics:

    •  — The format specifier must be empty, otherwise std::format_error is thrown, and

    •  — if v.valueless_by_exception() is true, std::bad_variant_access is thrown, and

    •  — the output is done as if by calling std::format_to(fmt_ctx.out(), proxy.v_fmt(std::in_place_type<Ts...[proxy.v.index()]>), UNWRAP_RECURSIVE(GET<proxy.v.index()>(proxy.v))), with Ts being the template parameter pack of the cv-unqualified non-reference type of proxy.v.

    • Example:

      using V = temp_ns::rvariant<int, double>;
      constexpr auto v_fmt = temp_ns::variant_format_for<V>("{:04d}", "{:.1f}");
      std::println("foo{}bar", temp_ns::format_by(v_fmt, V(42)); // prints foo0042bar
      std::println("foo{}bar", temp_ns::format_by(v_fmt, V(3.14)); // prints foo3.1bar
template<class... CharLike>
using select-char-t = see below; // exposition only
  • Denotes charT, with charT being the character type for which std::is_convertible_v<CharLikei, std::basic_string_view<charT>> is true for all i. If there exists no such substitution, the program is ill-formed.

// <temp_ns/rvariant/rvariant_io.hpp>

#include <format>

template<class charT, class... Ts>
struct variant_format_string // exposition only
{
  std::basic_format_string<charT, Ts...[i] const&> fmts...;
  auto const& operator()(std::in_place_type<Ts...[i]>) const noexcept { return fmts...[i]; }
};

namespace temp_ns {

template<class... Ts, class... Fmts>
constexpr variant_format_string<see below> variant_format(Fmts&&... fmts) noexcept;// 1

template<class Variant, class... Fmts>
constexpr variant_format_string<see below> variant_format_for(Fmts&&... fmts) noexcept;// 2

} // temp_ns
  • 1) Mandates: std::is_convertible_v<Fmtsi, std::basic_format_string<select-char-t<Fmts...>, Tsi const&>> is true for all i, and sizeof...(Ts) > 0 is true.

    Let charT denote select-char-t<Fmts...>.

    Returns: variant_format_string<charT, Ts...>{std::forward<Fmts>(fmts)...}.

  • 2) Mandates: std::is_convertible_v<Fmtsi, std::basic_format_string<select-char-t<Fmts...>, Tsi const&>> is true for all i, with Ts being the template parameter pack of cv-unqualified non-reference type for Variant. Such substitution is valid only if std::remove_cvref_t<Variant> is a specialization of rvariant.

    Let charT denote select-char-t<Fmts...>.

    Returns: variant_format_string<charT, Ts...>{std::forward<Fmts>(fmts)...}.

// <temp_ns/rvariant/rvariant_io.hpp>

#include <format>

template<class VFormat, class Variant>
struct variant_format_proxy // exposition only
{
    VFormat v_fmt;
    Variant v;
};

namespace temp_ns {

template<class VFormat, class Variant>
constexpr variant_format_proxy<VFormat, Variant>
format_by(VFormat&& v_fmt, Variant&& v) noexcept;// 1

} // temp_ns
  • 1) Constraints: std::remove_cvref_t<VFormat> is a specialization of variant_format_string, and std::remove_cvref_t<Variant> is a specialization of rvariant.

    Returns: variant_format_proxy<VFormat, Variant>{std::forward<VFormat>(v_fmt), std::forward<Variant>(v)}.

Class template recursive_wrapper [rvariant.recursive]

#include <compare>
#include <memory>

namespace temp_ns {

template<class T, class Allocator = std::allocator<T>>
class recursive_wrapper
{
  // provides the same functionality as std::indirect, unless otherwise noted

  // [rvariant.recursive.ctor], constructors
  constexpr /* not explicit */ recursive_wrapper();

  template<class U = T>
  constexpr explicit(see below) recursive_wrapper(U&& x) noexcept(see below);
};

// equivalent to the std::indirect counterpart
template<class Value>
  recursive_wrapper(Value) -> recursive_wrapper<Value>;

// equivalent to the std::indirect counterpart
template<class Allocator, class Value>
  recursive_wrapper(std::allocator_arg_t, Allocator, Value)
    -> recursive_wrapper<
      Value,
      typename std::allocator_traits<Allocator>::template rebind_alloc<Value>
    >;

} // temp_ns
// <temp_ns/rvariant/recursive_wrapper_pmr.hpp>

#include <memory_resource>

namespace temp_ns::pmr {

template<class T>
using recursive_wrapper = ::temp_ns::recursive_wrapper<T, std::pmr::polymorphic_allocator<T>>;

} // temp_ns::pmr

General [rvariant.recursive.general]

Unless otherwise noted, the class template temp_ns::recursive_wrapper and relevant components in the namespace scope provide same functionality and have equivalent requirements as std::indirect, except that:

  •  — The class name is recursive_wrapper.

  •  — std::indirect and temp_ns::recursive_wrapper are distinguishable in type level.

temp_ns::recursive_wrapper is not a type alias of std::indirect and does not publicly derive from it.
Although std::indirect is a C++26 feature, temp_ns::recursive_wrapper can be used in C++23.

Constructors

Effectively overrides only the ones listed below; rest are the same as std::indirect counterparts. [spec]

constexpr /* not explicit */ recursive_wrapper();// 1

template<class U = T>
constexpr explicit(see below) recursive_wrapper(U&& u) noexcept(see below);// 2
  • 1) Equivalent to the std::indirect counterpart,[spec] except that it is not explicit.

  • 2) Constraints:

    •  — std::is_same_v<std::remove_cvref_t<U>, recursive_wrapper> is false, and

    •  — std::is_same_v<std::remove_cvref_t<U>, std::in_place_t> is false, and

    •  — std::is_default_constructible_v<Allocator> is true, and

    •  — std::is_convertible_v<U, T> is true, or the expression T x[] = {std::forward<U>(u)} (for an imaginary variable x) is well-formed in unevaluated context. (Note: This prevents recursive instantiation of std::is_constructible, even for recursive types, while preserving SFINAE-friendliness.)

    Effects: Equivalent to the std::indirect counterpart. [spec]

    Remarks: The explicit specifier is equivalent to !std::is_convertible_v<U, T>.

recursive_wrapper helper classes [rvariant.recursive.helper]

namespace temp_ns {

template<class T>
struct unwrap_recursive
{
  using type = T;
};

template<class T, class Allocator>
struct unwrap_recursive<recursive_wrapper<T, Allocator>>
{
  using type = T;
};

} // temp_ns
template<class T>
constexpr see below UNWRAP_RECURSIVE(T&& o) noexcept; // exposition only
  • Effects: Denotes *o, if cv-unqualified non-reference type for T is a specialization of recursive_wrapper. Otherwise, denotes o.

Pack manipulation and deduping [rvariant.pack]

namespace temp_ns {

template<template<class...> class TT, class A, class B>
struct compact_alternative;

} // temp_ns
  • Effectively concatenates contained types in A and B, then dedupes them. If the resulting type list consists of only a single type, the surrounding template is unwrapped.

    Definition: Let Ts denote an imaginary pack of types where TT<Ts...> is the same type as pack-union-t<TT, A, B>. The member typedef type denotes Ts...[0] if sizeof...(Ts) == 1; otherwise, the member typedef type denotes TT<Ts...>.

Notes on single-type variant
A variant with a single alternative may introduce unnecessary overhead when used in many places where only the underlying type is actually needed. In such cases, the variant can be unwrapped using compact_alternative. This is useful for resolving issues such as boostorg/spirit#610.
compact_alternative does not unwrap recursive_wrapper. This is intentional, because doing so could lead to instantiating incomplete type on undesired timings. You may apply unwrap_recursive_t manually.

Exposition-only utilities [rvariant.xo]

This section demonstrates internal features used in the implementation.

Pack utilities [rvariant.xo.pack]

template<template<class...> class TT, class A, class B>
using pack-union-t = see below; // exposition only
  • Let As denote the pack of template parameters of A if A is a specialization of TT, otherwise let As denote a pack of single type A. Let Bs denote likewise. pack-union-t denotes TT<Ts...>, where Ts... is the set union of As... and Bs... expanded from left to right. For duplicate types, the first occurrence shall remain in Ts....

Core type traits [rvariant.xo.core]

template<class T, template<class...> class TT>
struct is-ttp-specialization-of;  // exposition only// 1

template<class T, template<auto...> class TT>
struct is-nttp-specialization-of; // exposition only// 2

template<class T, any-ttp>
struct is-specialization-of;      // exposition only// 3
  • 1-2) Inherits std::true_type if and only if T is a specialization of TT; otherwise, inherits std::false_type.

  • 3) If any-ttp is a template template parameter that consists of NTTP, equivalent to is-nttp-specialization-of; otherwise, equivalent to is-ttp-specialization-of.

is-specialization-of requires C++26 reflection for a straightforward resolution. For older versions, it can be worked around by a compound requires expression.

Additional Information

Benchmark

Non-recursive / Recursive

GCC / Clang / MSVC

About the Authors

Yaito Kakeyama is a C++ enthusiast with a strong interest in language design and modern library development. He has contributed to several public efforts in the C++ community, including co-authoring LWG 4166 with Nana Sakisaka and submitting occasional compiler bug reports. He is the co-author of rvariant and has been deeply involved in its implementation.

Nana Sakisaka has taken on an active maintainer role in Boost.Spirit since May 2025. The development of rvariant began as part of a broader effort to modernize the Boost.Spirit.X3 codebase. He is the co-author of rvariant and has focused on its rationale and specification wording.

License

This library is distributed under the Boost Software License, Version 1.0.