- Introduction
- Rationale
- Feature Cheat Sheet
- Reference [rvariant]
- General [rvariant.general]
- Header <iris/rvariant.hpp> synopsis [rvariant.syn]
- Class template
rvariant[rvariant.rvariant] rvarianthelper classes [rvariant.helper]- Flexibility traits [rvariant.flex]
- Value access [rvariant.get]
- Visitation [rvariant.visit]
- Hash support [rvariant.hash]
- I/O [rvariant.io]
- Class template
recursive_wrapper[rvariant.recursive] recursive_wrapperhelper utilities [rvariant.recursive.helper]
- Additional Information
rvariant is a standard-compatible variant library that supports recursive types.
// A common pattern for representing recursive ASTs using recursive variants.
struct BinaryExpr;
using Expr = iris::rvariant<int, double, iris::recursive_wrapper<BinaryExpr>>;
enum class Op;
struct BinaryExpr { Expr lhs, rhs; Op op{}; };
Expr expr{BinaryExpr{Expr{42}, Expr{3.14}}};
expr.visit(iris::overloaded{
[](int const&) { /* ... */ },
[](double const&) { /* ... */ },
[](BinaryExpr const&) { /* ... */ },
});-
GCC 14
-
Clang 21-22 (libc++)
-
MSVC 2022 and 2026
-
C++23 and C++26
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 to 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.
-
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.
-
Explore potential directions for future standardization, while gaining implementation experience with the modernized design.
|
||||
|---|---|---|---|---|
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 |
A recursive type is effectively an incomplete type at the point of its initial definition. However, a std-style variant class requires all alternatives 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.
Theoretically, a recursive wrapper class can be designed independently, i.e., with no relation to the variant class itself. So there’s no theoretical reason you couldn’t use 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 noted above, such unadapted wrappers introduce intrusive boilerplate that spreads to the application layer, which is undesirable in practice.
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 with underlines.
This section shows the pseudo code for all features in this library. You can click on the links to jump to the corresponding reference.
using A = int;
using B = double;
struct C {};
using AB = iris::rvariant<A, B>;
using BA = iris::rvariant<B, A>;
using ABC = iris::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 = iris::get<0>(ab);
A& a = iris::get<A>(ab);
C& c = iris::get<C>(ab); // throws std::bad_variant_access
A* a = iris::get_if<0>(&ab);
A* a = iris::get_if<A>(&ab);
C* c = iris::get_if<C>(&ab); // nullptr
// compatibility with boost; same effect as get_if
A* a = iris::get<0>(&ab);
A* a = iris::get<A>(&ab);
C* c = iris::get<C>(&ab); // nullptr
ab == ab;
ab < ab;
ab <=> ab; // requires (std::three_way_comparable<Ts> && ...)
auto visitor = iris::overloaded {
[](A const& a) {},
[](B const& b) {},
};
ab.visit(visitor); // member visit
iris::visit(visitor, ab); // function visit{
// flexible construction
AB ab{BA{}}; // unordered construction
ABC abc{AB{}}; // subset construction
// flexible assignment
ab = BA{}; // unordered assignment
abc = AB{}; // subset assignment
}
static_assert(iris::variant_size_v<AB> == 2);
static_assert(std::same_as<iris::variant_alternative_t<0, AB>, A>);
static_assert(iris::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 = ::iris::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 = iris::variant_format_for<V>("{:04d}", "{:.1f}");
std::println("foo{}bar", iris::format_by(v_fmt, V(42)); // prints foo0042bar
std::println("foo{}bar", iris::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 <iris/rvariant.hpp> synopsis [rvariant.syn]
namespace iris {
// [rvariant.rvariant], class template rvariant
template<class... Ts>
class rvariant;
// [rvariant.recursive], class template recursive_wrapper
template<class T>
class recursive_wrapper;
template<class T, class Allocator = std::allocator<T>>
class recursive_wrapper_alloca;
/* 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>
/* constexpr */ std::size_t hash_value(recursive_wrapper<T> const&);
template<class T, class Allocator>
/* constexpr */ std::size_t hash_value(recursive_wrapper_alloca<T, Allocator> const&);
// [rvariant.recursive.helper], recursive_wrapper helper classes
template<class T> using unwrap_recursive_type = see below;
template<class T> constexpr auto&& unwrap_recursive(T&& o) noexcept;
} // irisnamespace std {
// [rvariant.hash], hash support
template<class... Ts> struct hash<::iris::rvariant<Ts...>>;
// [rvariant.hash], hash support
template<class T, class Allocator> struct hash<::iris::recursive_wrapper<T, Allocator>>;
} // stdClass template rvariant [rvariant.rvariant]
namespace iris {
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&&);
};
} // irisGeneral [rvariant.rvariant.general]
|
Note
|
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
Tdenote an arbitrary type. For the template parameter ofrvariant, if a user provides bothTand any instantiation ofrecursive_wrapperorrecursive_wrapper_allocathat has thevalue_typeofT, the program is ill-formed. -
Let
Tdenote an arbitrary type. For the template parameter ofrvariant, if a user provides multiple different instantiations ofrecursive_wrapper_allocasuch that the first template parameter isT, the program is ill-formed.
|
Warning
|
Although rvariant<
int, recursive_wrapper<int>, recursive_wrapper_alloca<int, MyAllocator<int>>
> v(42); // error-prone; not allowed |
-
If
recursive_wrapper<Ti>orrecursive_wrapper_alloca<Ti, A>(with any typeA) occurs anywhere inTs..., letVTidenote that type; 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_type<VTi>, unwrap_recursive_type<Uj>>istrue. -
For the function that has the formal template parameter named
T: ifrecursive_wrapper<T>orrecursive_wrapper_alloca<T, A>(with any typeA) occurs anywhere inTs..., letVTdenote that type; otherwise, letVTdenoteT.
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-
4) Generic constructor. Equivalent to the
std::variantcounterpart, [spec] except:Postconditions:
holds_alternative<unwrap_recursive_type<Tj>>(*this)istrue. -
5) Mandates:
Tis not a specialization ofrecursive_wrapperorrecursive_wrapper_alloca.Constraints:
-
— There is exactly one occurrence of
Tinunwrap_recursive_type<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_wrapperorrecursive_wrapper_alloca.Constraints:
-
— There is exactly one occurrence of
Tinunwrap_recursive_type<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. -
-
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_type<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_type<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.
-
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-
3) Generic assignment operator. Equivalent to the
std::variantcounterpart, [spec] except:Postconditions:
holds_alternative<unwrap_recursive_type<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_type<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_type<Ti>, unwrap_recursive_type<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_type<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_type<Ti>, unwrap_recursive_type<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) Mandates:
Tis not a specialization ofrecursive_wrapperorrecursive_wrapper_alloca.Constraints:
std::is_constructible_v<VT, Args...>istrue, andToccurs exactly once inunwrap_recursive_type<Ts>.Effects: Equivalent to:
return emplace<I>(std::forward<Args>(args)...);
whereIis the zero-based index ofTinunwrap_recursive_type<Ts>. -
2) Mandates:
Tis not a specialization ofrecursive_wrapperorrecursive_wrapper_alloca.Constraints:
std::is_constructible_v<VT, std::initializer_list<U>&, Args...>istrue, andToccurs exactly once inunwrap_recursive_type<Ts>.Effects: Equivalent to:
return emplace<I>(il, std::forward<Args>(args)...);
whereIis the zero-based index ofTinunwrap_recursive_type<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_wrapperorrecursive_wrapper_alloca, 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_wrapperorrecursive_wrapper_alloca, 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;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&&>. -
rvariant helper classes [rvariant.helper]
namespace iris {
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
} // iris-
2) The member typedef
typedenotesunwrap_recursive_type<TI>.Mandates:
I < sizeof...(Ts).
Flexibility traits [rvariant.flex]
namespace iris::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>;
} // iris::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_type<T>is the same type asU.
-
Value access [rvariant.get]
namespace iris {
template<class T, class... Ts>
constexpr bool holds_alternative(rvariant<Ts...> const& v) noexcept;
} // iris-
Mandates: The type
Toccurs exactly once inunwrap_recursive_type<Ts>.Returns:
trueifv.index()is equal to the zero-based index ofTinunwrap_recursive_type<Ts>.Remarks: This function is defined as deleted if
Tis a specialization ofrecursive_wrapperorrecursive_wrapper_alloca.
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_wrapperorrecursive_wrapper_alloca; otherwise, returnsunwrap_recursive(o).
namespace iris {
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);
} // iris-
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 iris {
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);
} // iris-
Mandates: The type
Toccurs exactly once inunwrap_recursive_type<Ts>.Effects: Let
VTdenote the type of the alternative held byv. Ifunwrap_recursive_type<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_wrapperorrecursive_wrapper_alloca.
namespace iris {
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
} // iris-
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 iris {
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
} // iris-
1-2) Mandates: The type
Toccurs exactly once inunwrap_recursive_type<Ts>.Effects: Equivalent to:
return get_if<i>(v);with i being the zero-based index ofTinunwrap_recursive_type<Ts>.Remarks: This function is defined as deleted if
Tis a specialization ofrecursive_wrapperorrecursive_wrapper_alloca.
Visitation [rvariant.visit]
namespace iris {
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
} // iris
// 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 toiris::visitinstead ofstd::visit.
Hash support [rvariant.hash]
namespace std {
template<class... Ts>
struct hash<::iris::rvariant<Ts...>>;// 1
template<class T>
struct hash<::iris::recursive_wrapper<T>>;// 2
template<class T, class Allocator>
struct hash<::iris::recursive_wrapper_alloca<T, Allocator>>;// 3
} // stdnamespace iris {
template<class... Ts>
/* constexpr */ std::size_t hash_value(rvariant<Ts...> const& v);// 1
template<class T>
/* constexpr */ std::size_t hash_value(recursive_wrapper<T> const& rw);// 2
template<class T, class Allocator>
/* constexpr */ std::size_t hash_value(recursive_wrapper_alloca<T, Allocator> const& rw);// 3
} // iris-
1) Effects: Equivalent to
std::hash<rvariant<Ts...>>{}(v). -
2) Effects: Equivalent to
std::hash<recursive_wrapper<T>>{}(rw). -
3) Effects: Equivalent to
std::hash<recursive_wrapper_alloca<T, Allocator>>{}(rw).
I/O [rvariant.io]
I/O components are not included by the global convenience header (<iris/rvariant.hpp>).
// <iris/rvariant/rvariant_io.hpp>
namespace iris {
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
} // iris-
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_type<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. -
-
Let
vdenote an object ofrvariant, and letproxydenote an object ofvariant_format_proxy. -
The specialization
std::formatter<::iris::rvariant<Ts...>, charT>(for arbitrarycharT) is enabled if and only ifstd::formattable<unwrap_recursive_type<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("{}", iris::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 ofvariant_format_string, and -
—
std::remove_cvref_t<Variant>is a specialization ofrvariant, and -
—
std::formattable<unwrap_recursive_type<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 = iris::rvariant<int, double>; constexpr auto v_fmt = iris::variant_format_for<V>("{:04d}", "{:.1f}"); std::println("foo{}bar", iris::format_by(v_fmt, V(42)); // prints foo0042bar std::println("foo{}bar", iris::format_by(v_fmt, V(3.14)); // prints foo3.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.
// <iris/rvariant/rvariant_io.hpp>
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 iris {
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
} // iris-
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)...}.
// <iris/rvariant/rvariant_io.hpp>
template<class VFormat, class Variant>
struct variant_format_proxy // exposition only
{
VFormat v_fmt;
Variant v;
};
namespace iris {
template<class VFormat, class Variant>
constexpr variant_format_proxy<VFormat, Variant>
format_by(VFormat&& v_fmt, Variant&& v) noexcept;// 1
} // iris-
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]
namespace iris {
template<class T>
class recursive_wrapper
{
see below
};
} // irisClass template recursive_wrapper behaves like recursive_wrapper_alloca instantiated with std::allocator<T>, except that the allocator-related member functions (that is, member functions that take allocator-specific arguments) are omitted.
(Note: this is a QoL feature to discard the rarely used std::allocator<T> type parameter from diagnostic messages; see iris-cpp/iris#43.)
namespace iris {
template<class T, class Allocator = std::allocator<T>>
class recursive_wrapper_alloca
{
// provides the same functionality as std::indirect, unless otherwise noted
// [rvariant.recursive.ctor], constructors
constexpr /* not explicit */ recursive_wrapper_alloca();
template<class U = T>
constexpr /* not explicit */ recursive_wrapper_alloca(U&& x);
};
// equivalent to the std::indirect counterpart
template<class Value>
recursive_wrapper_alloca(Value) -> recursive_wrapper_alloca<Value>;
// equivalent to the std::indirect counterpart
template<class Allocator, class Value>
recursive_wrapper_alloca(std::allocator_arg_t, Allocator, Value)
-> recursive_wrapper_alloca<
Value,
typename std::allocator_traits<Allocator>::template rebind_alloc<Value>
>;
} // iris// <iris/rvariant/recursive_wrapper_pmr.hpp>
namespace iris::pmr {
template<class T>
using recursive_wrapper = ::iris::recursive_wrapper_alloca<
T, std::pmr::polymorphic_allocator<T>
>;
} // iris::pmrGeneral [rvariant.recursive.general]
Unless otherwise noted, the class template recursive_wrapper_alloca and relevant components in the namespace scope provide same functionality and have equivalent requirements as std::indirect.
|
Warning
|
recursive_wrapper and recursive_wrapper_alloca are not type alias of std::indirect and do not publicly derive from it.
|
|
Note
|
Although std::indirect is a C++26 feature, recursive_wrapper and recursive_wrapper_alloca can be used in C++23.
|
Effectively overrides only the ones listed below; rest are the same as std::indirect counterparts. [spec]
constexpr /* not explicit */ recursive_wrapper_alloca();// 1
template<class U = T>
constexpr /* not explicit */ recursive_wrapper_alloca(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_alloca>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 without depending on the fragile mutual-exclusiveness on
std::is_constructibleandstd::is_convertible. If you are aware of any technical insights, please contact us at iris-cpp/iris.
Effects: Equivalent to the
std::indirectcounterpart. [spec] -
recursive_wrapper helper utilities [rvariant.recursive.helper]
namespace iris {
template<class T>
using unwrap_recursive_type = see below;
} // iris-
Denotes
T::value_typeifTis a specialization ofrecursive_wrapperorrecursive_wrapper_alloca. Otherwise, denotesT.
namespace iris {
template<class T>
constexpr auto&& unwrap_recursive(T&& o) noexcept;
} // iris-
Returns:
*o, if cv-unqualified non-reference type forTis a specialization ofrecursive_wrapperorrecursive_wrapper_alloca. Otherwise, returnso.Remarks:
unwrap_recursiveis an algorithm function object ([alg.func.obj]).
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 courseEarly 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.
This library is distributed under the MIT License.