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.
|
Supported Environments
-
GCC 14
-
Clang 21-22 (libc++)
-
MSVC (2022)
-
C++23 and C++26
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
-
Provide a modern, efficient, and battle-tested recursive variant library to enable developers to avoid using Boost.Variant in new applications.
-
Replace existing usages of Boost.Variant in established frameworks, especially Boost.Spirit.X3.
-
Explore potential directions for future standardization, while gaining implementation experience with the modernized design.
Comparison of Variant Libraries
|
||||
|---|---|---|---|---|
Minimum C++ version |
C++23 |
C++17 |
C++11 |
C++11 |
Compilation speed |
Average |
Average |
Very Slow |
Average |
Interface for recursive types |
Yes |
No |
No |
|
May be valueless? |
Yes |
|||
Exception safety |
Basic exception safety |
Basic exception safety |
Strong exception safety |
Strong exception safety |
Flexible construction |
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()orvisit().
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]
-
In [rvariant],
GETdenotes a set of exposition-only function templates ([rvariant.get]).
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<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;
// [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
rvariantfollows all requirements ofstd::variant, unless otherwise noted. -
All types in
Tsmust satisfy all requirements on the corresponding parameter instd::variant, unless otherwise noted. -
Let
TandAdenote arbitrary types. For the template parameter ofrvariant, if a user provides bothTandrecursive_wrapper<T, A>, the program is ill-formed. -
Let
Tdenote an arbitrary type. For the template parameter ofrvariant, if a user provides multiple different specializations ofrecursive_wrappersuch that the first template parameter isT, the program is ill-formed.
|
Although
|
-
Let
VTidenoterecursive_wrapper<Ti, A>(for any typeA) if such a specialization occurs anywhere inTs...; otherwise, letVTidenoteTi. LetUjdenote the jth type of the template parameter pack having the nameUson each flexibility-related functions. The corresponding alternative forrvariantis the first type for whichstd::is_same_v<unwrap_recursive_t<VTi>, unwrap_recursive_t<Uj>>istrue.
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::variantcounterpart. [spec] -
4) Generic constructor. Equivalent to the
std::variantcounterpart, [spec] except:Postconditions:
holds_alternative<unwrap_recursive_t<Tj>>(*this)istrue. -
5) Mandates:
Tis not a specialization ofrecursive_wrapper.Let
VTdenoterecursive_wrapper<T, A>(for any typeA) if such a specialization occurs anywhere inTs...; otherwise, letVTdenoteT.Constraints:
-
— There is exactly one occurrence of
Tinunwrap_recursive_t<Ts>...and -
—
std::is_constructible_v<VT, Args...>istrue.
Effects: Direct-non-list-initializes the contained value of type
VTwithstd::forward<Args>(args)....Postconditions:
holds_alternative<T>(*this)istrue.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:
Tis not a specialization ofrecursive_wrapper.Let
VTdenoterecursive_wrapper<T, A>(for any typeA) if such a specialization occurs anywhere inTs...; otherwise, letVTdenoteT.Constraints:
-
— There is exactly one occurrence of
Tinunwrap_recursive_t<Ts>...and -
—
std::is_constructible_v<VT, std::initializer_list<U>&, Args...>istrue.
Effects: Direct-non-list-initializes the contained value of type
VTwithil, std::forward<Args>(args)....Postconditions:
holds_alternative<T>(*this)istrue.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::variantcounterpart. [spec] -
9) Flexible copy constructor.
Let
VTiandUjdenote the types defined in [rvariant.rvariant.general].Constraints:
-
—
std::is_same_v<rvariant<Us...>, rvariant>isfalse, and -
—
rvariant_set::subset_of<rvariant<Us...>, rvariant>istrue, and -
—
std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...>isfalse, and -
—
std::is_constructible_v<VTi, Uj const&>istruefor all j.
Effects: If
wholds a value, initializes thervariantto holdVTi(with i being the index of the alternative corresponding to that ofw) and direct-initializes the contained value withGET<w.index()>(w). Otherwise, initializes thervariantto 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
ANDofstd::is_nothrow_constructible_v<VTi, Uj const&>for all j.
-
-
10) Flexible move constructor.
Let
VTiandUjdenote the types defined in [rvariant.rvariant.general].Constraints:
-
—
std::is_same_v<rvariant<Us...>, rvariant>isfalse, and -
—
rvariant_set::subset_of<rvariant<Us...>, rvariant>istrue, and -
—
std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...>isfalse, and -
—
std::is_constructible_v<VTi, Uj&&>istruefor all j.
Effects: If
wholds a value, initializes thervariantto holdVTi(with i being the index of the alternative corresponding to that ofw) and direct-initializes the contained value withGET<w.index()>(std::move(w)). Otherwise, initializes thervariantto 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
ANDofstd::is_nothrow_constructible_v<VTi, Uj&&>for all j.
-
Destructor [rvariant.dtor]
constexpr ~rvariant();// 1
-
1) Equivalent to the
std::variantcounterpart. [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::variantcounterpart. [spec] -
3) Generic assignment operator. Equivalent to the
std::variantcounterpart, [spec] except:Postconditions:
holds_alternative<unwrap_recursive_t<Tj>>(*this)istrue, withTjselected by the imaginary function overload resolution described above. -
4) Flexible copy assignment operator.
Let
VTiandUjdenote the types defined in [rvariant.rvariant.general].Constraints:
-
—
std::is_same_v<rvariant<Us...>, rvariant>isfalse, and -
—
rvariant_set::subset_of<rvariant<Us...>, rvariant>istrue, and -
—
std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...>isfalse, and -
—
std::is_constructible_v<VTi, Uj const&> && std::is_assignable_v<VTi&, Uj const&>istruefor all j.
Effects: Let j be
rhs.index().-
— If neither
*thisnorrhsholds a value, there is no effect. -
— Otherwise, if
*thisholds a value butrhsdoes not, destroys the value contained in*thisand sets*thisto not hold a value. -
— Otherwise, if
rhsholds a value but*thisdoes not, initializesrvariantto holdVTi(with i being the index of the alternative corresponding to that ofrhs) and direct-initializes the contained value withGET<j>(rhs). -
— Otherwise, if
std::is_same_v<unwrap_recursive_t<Ti>, unwrap_recursive_t<Uj>>istrue, assignsGET<j>(rhs)to the value contained in*this. (Note: the left hand side isTi, notVTi. This ensures that the existing storage is reused even forrvariantwith duplicate corresponding alternatives; i.e.index()is unchanged.) -
— Otherwise, if either
std::is_nothrow_constructible_v<VTi, Uj const&>istrueorstd::is_nothrow_move_constructible_v<VTi>isfalse, equivalent toemplace<VTi>(GET<j>(rhs)). -
— Otherwise, equivalent to
emplace<VTi>(VTi(GET<j>(rhs))).
Postconditions: If
rhs.valueless_by_exception()istrue,index() == rhs.index(); otherwise,*thisholds the alternative corresponding to that ofrhs.Returns:
*this.Remarks: The exception specification is equivalent to the logical
ANDofstd::is_nothrow_constructible_v<VTi, Uj const&> && std::is_nothrow_assignable_v<VTi&, Uj const&>for all j. -
-
5) Flexible move assignment operator.
Let
VTiandUjdenote the types defined in [rvariant.rvariant.general].Constraints:
-
—
std::is_same_v<rvariant<Us...>, rvariant>isfalse, -
—
rvariant_set::subset_of<rvariant<Us...>, rvariant>istrue, and -
—
std::disjunction_v<std::is_same<rvariant<Us...>, unwrap_recursive_t<Ts>>...>isfalse, and -
—
std::is_constructible_v<VTi, Uj&&> && std::is_assignable_v<VTi&, Uj&&>istruefor all j.
Effects: Let j be
rhs.index().-
— If neither
*thisnorrhsholds a value, there is no effect. -
— Otherwise, if
*thisholds a value butrhsdoes not, destroys the value contained in*thisand sets*thisto not hold a value. -
— Otherwise, if
rhsholds a value but*thisdoes not, initializesrvariantto holdVTi(with i being the index of the alternative corresponding to that ofrhs) and direct-initializes the contained value withGET<j>(std::move(rhs)). -
— Otherwise, if
std::is_same_v<unwrap_recursive_t<Ti>, unwrap_recursive_t<Uj>>istrue, assignsGET<j>(std::move(rhs))to the value contained in*this. (Note: the left hand side isTi, notVTi. This ensures that the existing storage is reused even forrvariantwith 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
ANDofstd::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
VTdenoterecursive_wrapper<T, A>(for any typeA) if such a specialization occurs anywhere inTs...; otherwise, letVTdenoteT.Mandates:
Tis not a specialization ofrecursive_wrapper.Constraints:
std::is_constructible_v<VT, Args...>istrue, andToccurs exactly once inunwrap_recursive_t<Ts>.Effects: Equivalent to:
return emplace<I>(std::forward<Args>(args)...);
whereIis the zero-based index ofTinunwrap_recursive_t<Ts>. -
2) Let
VTdenoterecursive_wrapper<T, A>(for any typeA) if such a specialization occurs anywhere inTs...; otherwise, letVTdenoteT.Mandates:
Tis not a specialization ofrecursive_wrapper.Constraints:
std::is_constructible_v<VT, std::initializer_list<U>&, Args...>istrue, andToccurs exactly once inunwrap_recursive_t<Ts>.Effects: Equivalent to:
return emplace<I>(il, std::forward<Args>(args)...);
whereIis the zero-based index ofTinunwrap_recursive_t<Ts>. -
3) Equivalent to the
std::variantcounterpart, [spec] except:Returns: Let
odenote a reference to the new contained value. ReturnsUNWRAP_RECURSIVE(o).Remarks: If
TIis a specialization ofrecursive_wrapper, this function is permitted to construct an intermediate variabletmpas if by passingstd::forward<Args>(args)...toTI's constructor. Thenrvariantdirect-non-list-initializes the contained value ofTIwith the argumentstd::move(tmp). (Note: This allows optimization wherervariantcan be assumed to become never valueless on certain cases.) -
4) Equivalent to the
std::variantcounterpart, [spec] except:Returns: Let
odenote a reference to the new contained value. ReturnsUNWRAP_RECURSIVE(o).Remarks: If
TIis a specialization ofrecursive_wrapper, this function is permitted to construct an intermediate variabletmpas if by passingil, std::forward<Args>(args)...toTI's constructor. Thenrvariantdirect-non-list-initializes the contained value ofTIwith the argumentstd::move(tmp). (Note: This allows optimization wherervariantcan 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>istruefor all j, whereUjbe the jth type inUs.Effects: If
*thisholds a value, returns anrvariant<Us...>object that holds the alternative corresponding to that of*this, with its contained value direct-initialized fromGET<i>(*this), where i isthis->index(). Otherwise, returns anrvariant<Us...>object that does not hold a value.Throws:
std::bad_variant_accessif*thisholds an alternative that is not contained inUs; 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>istrue. -
— The exception specification is equivalent to the logical
ANDofrvariant_set::equivalent_to<rvariant<Us...>, rvariant>andstd::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>istruefor all j, whereUjbe the jth type inUs.Effects:
GET<i>(*this)is replaced withGET<i>(std::move(*this)).Remarks:
std::is_nothrow_constructible_v<rvariant<Us...>, rvariant const&>is replaced withstd::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::variantcounterpart. [spec] -
2) The member typedef
typedenotesunwrap_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
WandVare specialization ofrvariant. -
2) Constraints: For every type
UinUs, there exists at least one typeTinTssuch that:-
—
Tis the same type asUor, -
—
unwrap_recursive_t<T>is the same type asU.
-
Value access [rvariant.get]
namespace temp_ns {
template<class T, class... Ts>
constexpr bool holds_alternative(rvariant<Ts...> const& v) noexcept;
} // temp_ns
-
Mandates: The type
Toccurs exactly once inunwrap_recursive_t<Ts>.Returns:
trueifv.index()is equal to the zero-based index ofTinunwrap_recursive_t<Ts>.Remarks: This function is defined as deleted if
Tis a specialization ofrecursive_wrapper.
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()isI.Returns:
o, whereodenotes a reference to the object stored inv, if the type of the expression’s receiver is a specialization ofrecursive_wrapper; otherwise, returnsUNWRAP_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()isI, returnsUNWRAP_RECURSIVE(o), whereodenotes a reference to the object stored in thervariant. Otherwise, throws an exception of typestd::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
-
Mandates: The type
Toccurs exactly once inunwrap_recursive_t<Ts>.Effects: Let
VTdenote the type of the alternative held byv. Ifunwrap_recursive_t<VT>is the same type asT, returnsUNWRAP_RECURSIVE(o), whereodenotes a reference to the object stored in thervariant. Otherwise, throws an exception of typestd::bad_variant_access.Remarks: This function is defined as deleted if
Tis a specialization ofrecursive_wrapper.
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
} // temp_ns
-
1-2) Mandates:
I < sizeof...(Ts).Returns: A pointer to the value denoted by
UNWRAP_RECURSIVE(o), whereodenotes a reference to the object stored in thervariant, ifv != nullptrandv->index() == I. Otherwise, returnsnullptr.
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
} // temp_ns
-
1-2) Mandates: The type
Toccurs exactly once inunwrap_recursive_t<Ts>.Effects: Equivalent to:
return get_if<i>(v);with i being the zero-based index ofTinunwrap_recursive_t<Ts>.Remarks: This function is defined as deleted if
Tis a specialization ofrecursive_wrapper.
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::variantcounterpart [spec], except that:-
—
GET<m>(std::forward<V>(vars))is replaced withUNWRAP_RECURSIVE(GET<m>(std::forward<V>(vars))).
-
-
3-4) Equivalent to the
std::variantcounterpart [spec], except that it forwards totemp_ns::visitinstead ofstd::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
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
trueif all of the following conditions are met; otherwise, evaluates tofalse.-
— Let
osdenote an lvalue reference to an object of typestd::ostream, and letvaldenote an lvalue reference to an object of typeT. These references are valid in unevaluated context, and -
— the expression
os << valis well-formed and has the typestd::ostream&, and -
— the corresponding overload is found solely via ADL.
-
-
2) Constraints:
ADL-ostreamable<unwrap_recursive_t<Ti>>istruefor 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 beingv.index()), and -
— any exception of type
std::bad_variant_access, whether thrown directly (i.e. due tovbeing valueless) or indirectly (i.e. by a nested call to an alternative’s output function), is propagated without regard to the value ofos.exceptions()and without turning onstd::ios_base::badbitin the error state ofos.
Returns:
os.Throws:
std::bad_variant_accessifv.valueless_by_exception()istrue. Otherwise, throws any exception thrown as per the formatted output function’s specification. -
std::formatter support
-
Let
vdenote an object ofrvariant, and letproxydenote an object ofvariant_format_proxy. -
The specialization
std::formatter<::temp_ns::rvariant<Ts...>, charT>(for arbitrarycharT) is enabled if and only ifstd::formattable<unwrap_recursive_t<Tsi>, charT>istruefor all i, with the following characteristics:-
— The format specifier must be empty, otherwise
std::format_erroris thrown, and -
— if
v.valueless_by_exception()istrue,std::bad_variant_accessis thrown, and -
— the output is done as if by calling
std::format_to(fmt_ctx.out(), paren, UNWRAP_RECURSIVE(GET<v.index()>(v))), withparenbeing a string literal"{}"interpreted on the target character type. -
Example:
std::println("{}", temp_ns::rvariant<int, double>(42)); // prints42
-
-
The specialization
std::formatter<variant_format_proxy<VFormat, Variant>, charT>is enabled if and only if:-
—
std::remove_cvref_t<VFormat>is a specialization ofvariant_format_string, and -
—
std::remove_cvref_t<Variant>is a specialization ofrvariant, and -
—
std::formattable<unwrap_recursive_t<Tsi>, charT>istruefor all i, withTsbeing the template parameter pack of cv-unqualified non-reference type forVariant.
-
-
It has the following characteristics:
-
— The format specifier must be empty, otherwise
std::format_erroris thrown, and -
— if
v.valueless_by_exception()istrue,std::bad_variant_accessis 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))), withTsbeing the template parameter pack of the cv-unqualified non-reference type ofproxy.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)); // printsfoo0042barstd::println("foo{}bar", temp_ns::format_by(v_fmt, V(3.14)); // printsfoo3.1bar
-
template<class... CharLike>
using select-char-t = see below; // exposition only
-
Denotes
charT, withcharTbeing the character type for whichstd::is_convertible_v<CharLikei, std::basic_string_view<charT>>istruefor 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&>>istruefor all i, andsizeof...(Ts) > 0istrue.Let
charTdenoteselect-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&>>istruefor all i, withTsbeing the template parameter pack of cv-unqualified non-reference type forVariant. Such substitution is valid only ifstd::remove_cvref_t<Variant>is a specialization ofrvariant.Let
charTdenoteselect-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 ofvariant_format_string, andstd::remove_cvref_t<Variant>is a specialization ofrvariant.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 /* not explicit */ recursive_wrapper(U&& x);
};
// 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::indirectandtemp_ns::recursive_wrapperare 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 /* not explicit */ recursive_wrapper(U&& u);// 2
-
1) Equivalent to the
std::indirectcounterpart,[spec] except that it is notexplicit. -
2) Constraints:
-
—
std::is_same_v<std::remove_cvref_t<U>, recursive_wrapper>isfalse, and -
—
std::is_same_v<std::remove_cvref_t<U>, std::in_place_t>isfalse, and -
—
std::is_default_constructible_v<Allocator>istrue, and -
—
std::is_convertible_v<U, T>istrue.Note 1: This prevents recursive instantiation of
std::is_constructible, even for recursive types, while preserving SFINAE-friendliness. This specification is technically viable only because the class templatervariantnever usesstd::is_convertiblein any of its constructor overloads. As a result, the atomic constraints ofrvariantandrecursive_wrapperremain mutually exclusive. However, if a user-defined class depends on bothstd::is_constructibleandstd::is_convertible(for the samervariantspecialization), it may trigger recursive instantiation.Note 2: It is currently unknown whether the recursive instantiation scenario described in Note 1 can be technically avoided. This note is provided for informational purposes only and does not specify the semantics of
recursive_wrapper.
Effects: Equivalent to the
std::indirectcounterpart. [spec] -
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 forTis a specialization ofrecursive_wrapper. Otherwise, denoteso.
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
AandB, then dedupes them. If the resulting type list consists of only a single type, the surrounding template is unwrapped.Definition: Let
Tsdenote an imaginary pack of types whereTT<Ts...>is the same type aspack-union-t<TT, A, B>. The member typedeftypedenotesTs...[0]ifsizeof...(Ts) == 1; otherwise, the member typedeftypedenotesTT<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
Asdenote the pack of template parameters ofAifAis a specialization ofTT, otherwise letAsdenote a pack of single typeA. LetBsdenote likewise.pack-union-tdenotesTT<Ts...>, whereTs...is the set union ofAs...andBs...expanded from left to right. For duplicate types, the first occurrence shall remain inTs....
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_typeif and only ifTis a specialization ofTT; otherwise, inheritsstd::false_type. -
3) If
any-ttpis a template template parameter that consists of NTTP, equivalent tois-nttp-specialization-of; otherwise, equivalent tois-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
The benchmark application (source code) measures the performance of rvariant construction and access using randomly generated inputs.
It uses std::mt19937 for random number generation and constructs rvariant instances directly within a std::vector. For the std::string benchmarks, we simply pass the stringized random numbers.
The alternative index is distributed randomly during the construction, making sure that the access to internal union is not overly optimized by the compiler.
Benchmark Environment
-
Date: 2025/8/9
-
CPU: Intel Core i9-14900KF
-
RAM: Crucial Pro 48GB DDR5-5600 x 4
GCC
Clang
MSVC
Benchmark Analysis
The graphs show that the cost of read-only access is remarkably small compared to the time spent on construction.
Read operations on a variant complete in constant time, as runtime dispatch is limited by the small, fixed number of alternatives. Benchmarks show std::variant and rvariant perform similarly, with read times unaffected by whether there are 3 or 16 alternatives and not scaling with N.
Visitation Technique in Depth
Some readers might assume that table-based visitation is the ideal approach, implying that visitations have constant time complexity.
using overload_type = R(*)(Visitor&&, Storage&&);
constexpr overload_type vtable[] = {
&do_visit<0, Visitor, Storage>,
&do_visit<1, Visitor, Storage>,
&do_visit<2, Visitor, Storage>,
...
};
vtable[v.index()](vis, v.storage()); // O(1), of course
Early std::variant implementations used this function-pointer-based dispatch, but it was later found that this pattern results in poor inlining in major compilers, whereas switch-case-based dispatch is significantly better optimized (link). GCC, LLVM, and MSVC subsequently adopted the latter approach.
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.