Das Gut2 Buch - variant

Current status

Future direction

CTAD construction
Use type arguments for the inactive member types.
Alternative active index layout
If we have an array of variants, we may want to store the indices in a separate array, for example. Or maybe we want to have partitioned arrays of variants based on active index type, and so resolve the active member object by array position rather than querying each variant.
Alternative layouts
Such as layouts optimized for arrays, stacks and deques/rings.
Nonlocal optional for values returned via structured binding.
If nonlocal optional is available, we can somewhat track the variant's active member object, as the returned nonlocal optional can use a reference to the index as its own index.

Forward declarations

template <typename... MemberTypes> struct variant ;
The main template providing a safely managed discriminated union.

Unions are used when we want some runtime dependent determination of an object's type, but we don't need any kind of polymorphic dispatch. Maybe we don't want to pay the price of a vtable. Maybe we don't want to have a type hierarchy if the problem being addressed only requires a small, known, set of types.

One common use case is for reliably reporting errors. A function returns either a result, or an error type. The advantage of using a union type in this case is that there can be only one valid interpretation. So unlike returning, say, a pair with an error object and a placeholder for the normal return object, we just need to create the one object. This avoids mistakenly accessing an invalid object and saves time and space.

Discriminated unions provide safe management by keeping track of what the real object is by means of an index. By using the higher order functions like map and each, we can access the real object with absolute safety. You can still unsafely access the object if you know the index of the real object.

template <index_t, typename...> union variant_union ;
Undefined primary template definition. See variant_union.
template <index_t MemberIndex> union variant_union <MemberIndex>;
Exit condition for recursively defined union.

Maintains trivial destructibility just in case. See test here.

template <index_t MemberIndex, typename MemberType, typename... MemberTypes> union variant_union <MemberIndex, MemberType, MemberTypes...>;
Programmatically defines a union by recursion.

This recursive definition is defined as a specialization to make the exit condition easier to define. See variant_union.

template <typename Variant, bool Trivial = true> struct variant_destructor ;
Base class to preserve trivial destructibility. Primary template definition.

When all member types are trivially destructible, we can make the whole variant trivially destructible. This is done by leaving the destructor undefined. This precludes explicitly defaulting the destructor as it will actually define it and lose trivial destructibility.

Hence why base class dispatch is needed to handle trivial and non-trivial variants.

template <typename Variant> struct variant_destructor <Variant, false>;
Defines destructor if variant has at least one non-trivial member type.

unions don't keep track of their active member type (except under constexpr evaluation), so destructors won't be automatically called if the active member is not trivially destructible. The overarching variant already keeps track of the index, so we shouldn't duplicate what already exists.

Hence this template base class uses the CRTP technique to call the deriving variant's reset method. As previously established, it cannot be a destructor - the reason why we have base class dispatch in the first place.

template <typename... MemberTypes> struct variant_index_base ;
Primary base template class supplying a type for the index for the active member object.

For a typical variant with more than one member type, the index is an integer with the minimum number of bits required to specify the active member.

template <typename MemberType> struct variant_index_base <MemberType>;
Specialized base for providing variant index with one member type.

We don't waste space on an index when a variant has only one member type, since there can only be one possible valid member object. See test here.

SFINAE traits

template <typename...> inline constexpr constant is_variant_v = constant_v<false>;
template <typename... MemberTypes> inline constexpr constant is_variant_v<variant<MemberTypes...>> = constant_v<true>;
Can be used for disambiguating between copy or move constructor, and constructor taking one argument of forwarding reference type.

Structure definitions, Member function declarations

template <index_t MemberIndex> union variant_union <MemberIndex>

constexpr variant_union() = default;
constexpr variant_union(const variant_union &) = default;
constexpr variant_union(variant_union &&) = default;
constexpr variant_union &operator=(const variant_union &) = default;
constexpr variant_union &operator=(variant_union &&) = default;
The special member functions are explicitly defaulted for documentation purposes.

destructor not defaulted to preserve constexpr trivial destructibility of the union.

template <index_t MemberIndex, typename MemberType, typename... MemberTypes> union variant_union <MemberIndex, MemberType, MemberTypes...>

using head_type = thing<MemberType>;
using tail_type = variant_union<MemberIndex + 1, MemberTypes...>;
head_type m_head;
tail_type m_tail;

unions cannot inherit from types, and cannot be inherited from, so we cannot use the ntuple trick of "inheriting" from parameter packs to make indexable base classes. Therefore member types must be declared as union members. Parameter packs cannot be expanded to create a list of union members, since they are statements, not a comma separated list.

We have to revert to a classic LISP composition: the first member is the head of the first type of the member type list, and the second member is a union representing the rest of the member types.

From this, we can encode the index of the union member as part of the union template's type and leave the storage of the active member index to the overarching variant.

constexpr variant_union() = default;
constexpr variant_union(const variant_union &) = default;
constexpr variant_union(variant_union &&) = default;
constexpr variant_union &operator=(const variant_union &) = default;
constexpr variant_union &operator=(variant_union &&) = default;
The special member functions are explicitly defaulted for documentation purposes.

destructor not defaulted to preserve constexpr trivial destructibility of the union.

template <typename CandidateType> constexpr variant_union(index<MemberIndex>, CandidateType &&that);
Found the correct union member to make active with given object.

When the given index argument matches this recursive union's specified index, type deduction will select this constructor overload which will stop recursively searching for the next candidate. This union's m_head is made the active member object. This does the heavy work of construction, while variant may supply the index by deduction, or directly specified by the user.

See variant_union.

template <index_t CandidateIndex, typename CandidateType> constexpr variant_union(index<CandidateIndex>, CandidateType &&that);
Recursively find the correct union member to make active with given object.

When the given index argument does not match this recursive union's specified index, type deduction will select this constructor overload. All it does is to try to construct m_tail, in effect performing a recursive search.

See variant_union.

template <index_t CandidateIndex, typename CandidateType> constexpr decltype(auto) operator= (forwarding_ntuple<index<CandidateIndex>, CandidateType> that);
Recursively find the correct union to assign to.

See variant_union and variant_union.

The overarching variant handles all the active member index resolution, either with a user supplied index, or type to active member index resolution via a reduce. This is mainly used when the active member object is the one being assigned to. If this union is entirely trivially assignable, then we just use the default assignment for the union.

If that is not the case, the active member object must be destroyed first. That leaves the union in a very iffy state and so must quickly be reconstructed with the placement new. This happens in the overarching variant, since it maintains the index and knows where the active member object is.

template <index_t Index> constexpr decltype(auto) value(index<Index> = {}) const;
template <index_t Index> constexpr decltype(auto) value(index<Index> = {});
Unsafely finds the member object at the given index.

The index is either user supplied, in which case it is unsafe if it doesn't address the active member object; or it is deduced by the overarching variant from a given type.

template <index_t Index> constexpr void reset(index<Index> = {});

Call this instead of the destructor because the destructor can't take an index or any other argument.

template <typename Variant> struct variant_destructor <Variant, false>

~variant_destructor();
Resets the derived variant.

Only used when the derived variant is not wholly trivially default destructible.

template <typename... MemberTypes> struct variant_index_base

constexpr variant_index_base(index_t i);
Must provide index, since we can't risk not properly contructing the variant.

This disables trivial default constructibility, but that's okay, since variant isn't trivially default constructible anyway. See variant.

constexpr decltype(auto) index() const;
The non-negative active member index.
integer<max_bits(sizeof...(MemberTypes)) + 1> m_index;
The index of the active member object.

In the case of duplicated member types, the active member index can be thought of as signifying a role. For example, in a variant with two string member types, one could represent a result, while the other an error message.

As always, we have one bit extra, because we standardize on signed numbers in this library, and negative numbers are a nice error signal. Why not just use a std::intmax_t? To save space. Most variants probably won't exceed eight member types anyway. So limiting the bit count to as low as possible means errors show up more earlier.

template <typename MemberType> struct variant_index_base <MemberType>

constexpr variant_index_base(index_t);
A no-op constructor since we don't store the index.

The constructor is defined to make it easier for the variant's constructor to be generic. This disables trivial default constructibility, but that's okay, since variant isn't trivially default constructible anyway. See variant.

constexpr decltype(auto) index() const;
For a single member type variant, the index is always 0.

template <typename... MemberTypes> struct variant

Type traits

using union_type = variant_union<0, MemberTypes...>;
The union type used to store the member objects.
using index_base = variant_index_base<MemberTypes...>;
The base class provider of the index.
constexpr static decltype(auto) count();
Number of member objects supported by this variant.

The returned count includes all duplicated types, since it places a limit on the index, which is also agnostic towards duplicated member types.

Explicitly defaulted constructors and assignments

constexpr variant(const variant &) = default;
constexpr variant(variant &&) = default;
constexpr variant &operator=(const variant &) = default;
constexpr variant &operator=(variant &&) = default;
The special member functions are explicitly defaulted for documentation purposes.
template <index_t DefaultIndex = match_indices<std::is_default_constructible>( make_type_list<MemberTypes...>())[index_v<0>], typename DefaultType = std::enable_if_t<(DefaultIndex > -1)>> constexpr variant();
Default constructor, if possible to default construct.

Default constructs the variant with the first default constructible member type. See test here. If it is not possible to default construct, then use one of the other constructors that takes arguments to construct the desired active member object.

Specially defined special member functions

template < typename CandidateType, typename = std::enable_if_t<!is_variant_v<remove_cvref_t<CandidateType>>>> constexpr variant(CandidateType &&that);
Construct a variant with an object of a member type.

The actual object constructed is the first member type that can be constructed from the argument.

SFINAE'd to avoid clashing with copy and move constructors.

These are the type matching priorities (See match_assign):

  1. Exact type match.
  2. Member type is constructible from forwarded object.
template <index_t CandidateIndex, typename CandidateType> constexpr variant(gut::index<CandidateIndex>, CandidateType &&that);
Construct a variant for a given member type index.

This constructor is helpful if the variant is defined with multiple same member types. Sometimes it is a neat trick to have multiple member types of the same type differentiated only by their index. One use case for this is if you don't want to create a new type for a very small use case, so instead you just use the same type, but have the index denote some kind of role.

Member type at given index must be constructible from the given object.

template <typename CandidateType, typename = std::enable_if_t<type_v<variant> != remove_cvref_v<CandidateType>>> constexpr decltype(auto) operator= (CandidateType &&that);
Assign a new active member object for the earliest suitable member type.

See variant. The only difference is the check for constructibility is replaced with a check for assignability.

These are the type matching priorities (See match_assign):

  1. Exact type match.
  2. Member type is assignable from forwarded object.
template <index_t CandidateIndex, typename CandidateType> constexpr decltype(auto) operator= (forwarding_ntuple<gut::index<CandidateIndex>, CandidateType> that);
Assign a new active member object for given index.

See variant. The only difference is the check for constructibility is replaced with a check for assignability. forwarding_tuple makes it possible to do indexed assignment as the regular assignment operator can't take more than one argument.

Assignment poses an extra level of difficulty, since it may require destroying the existing active object first. Destructors are not constexpr friendly. Then it may require creating a new object in its place, and the only safe way to do it is with placement new, which is also not constexpr friendly.

Fortunately, we can avoid destruction and reconstruction if the replacment object is the same type as the active object. In that case, we can just assign. A further bit of fortune is that if the variant is trivially assignable, then we can just move or copy assign the given object as a union of the same type.

using index_base::index;
Dispatches to base class providing the index.

See variant_index_base.

Active member accessors

template <index_t MemberIndex> constexpr decltype(auto) value(gut::index<MemberIndex> = {}) const;
template <index_t MemberIndex> constexpr decltype(auto) value(gut::index<MemberIndex> = {});
template <typename MemberType> constexpr decltype(auto) value(type<MemberType> = {}) const;
template <typename MemberType> constexpr decltype(auto) value(type<MemberType> = {});
Unsafely retrieves the value of the object for a given member index or type.

These access methods are unsafe because they assume that the active member is resolvable to the request. It is undefined behaviour if it isn't.

The value can be addressed either via index, or by type. They can be called either with explicitly provided template arguments, or deduced from the function argument. If addressing by type and the valid object is convertible to that type, it is returned with the requested type even if it is not an exact match. See test here and here.

The type qualifiers that the member type was declared as is preserved. See test here and here.

An unqualified type is returned as an l-value reference. See test here.

For const-qualified overload, all retrieved values are const. See test here. For the non-const overload, only the member types declared as const retain their const-ness. See test here.

template <index_t MemberIndex> constexpr decltype(auto) get(gut::index<MemberIndex> = {}) const;
template <index_t MemberIndex> constexpr decltype(auto) get(gut::index<MemberIndex> = {});
template <typename MemberType> constexpr decltype(auto) get(type<MemberType> = {}) const;
template <typename MemberType> constexpr decltype(auto) get(type<MemberType> = {});
Structured binding support. See value instead.

Just like optional, structured binding returns the index of active member object as well.

High order functions

template <typename Map> constexpr decltype(auto) map(Map &&m) const;
Transforms an object of one of the member types to another.

A corresponding variant, wrapped in an optional, is returned with member types as if the supplied mapping function was applied to all of this variant's member types. It is an optional because the supplied mapping function may not have acted on the active member object, and would have otherwise resulted in a variant without an active member object.

The supplied mapping function is only called for the active member object, but the supplied mapping function can choose to consider any, all, or none of the member types. The mapping function can take equally a forwarding reference or the exact type it wants to consider. Only if the mapping function is callable with the active member object will it then be called. If using a forwarding reference, the mapping function is still free to ignore any potential type of active member object it wants.

If the mapping function returns void for a potential object at its given index, or is not even callable for the potential object, then a something type is resolved for its respective index in the resulting variant. See test here. If the return type is a reference of some sort, its reference category is preserved. See test here and here. We want to be as generic as possible, so if the mapping function returns a reference, we assume it must have had a good reason to do so. It is the mapping function's responsibility to not return invalid references in this case.

The supplied mapping function is prevented from modifying the active member object.

If the supplied function has lambda captures, there is nothing stopping it from leaking the active object as a reference. It is undefined behaviour if the variant changes to a different active member.

template <typename Each> constexpr decltype(auto) each(Each &&e) const;
template <typename Each> constexpr decltype(auto) each(Each &&e);
Perform an action on the active member object.

If the supplied function returns an object of any type, it is ignored. The variant itself is returned, so as to allow the chaining of functions into a pipeline.

If the supplied function has lambda captures, there is nothing stopping it from leaking the active object as a reference. It is undefined behaviour if the variant changes to a different active member in much the same way accessing an object with the wrong reference type is undefined behaviour.

There is both a const and non-const version because the supplied function may want to, say, call a non-const member function on the active member object, and we don't want to prevent that. It is the responsibility of the supplied function to not modify an object if it shouldn't due to other business logic.

template <template <typename> typename Filter> constexpr decltype(auto) filter() const;
Removes the member types that does not match specific criteria at compile time.

Much like map, the remaining member types may not be the type of the active member object, so it similarly returns an optional wrapped variant to cover the case that would otherwise leave a variant with no active member object. If the active member object's type is in the resulting variant, the result will have a copy of the active member object.

No criteria function can be supplied because we need to maintain constexpr-ness, and it can be confusing to require a user to write a function that can only be used in a std::declval context. It also avoids the confusion that the criteria function can somehow depend on the runtime value to inform the necessarily compile-time result.

template <typename... Args> constexpr decltype(auto) operator()(Args &&... args) const;
template <typename... Args> constexpr decltype(auto) operator()(Args &&... args);
Calls the active member object with the supplied arguments.

Only the member types that can be called with the supplied arguments are considered. Of those, only if the active member object is of those types is it called. Arguments are forwarded to allow the active member object to do what it really wants to do. It is the responsibility of the user to ensure if any arguments are moved, that they are only used in the manner guaranteed by the language.

Just like with map, an optional wrapped variant of the resulting types, as if all callable member types was called, is returned.

Like with each, both const and non-const qualified overload is supplied in order to be the most generalized use. The active member object may itself have const and non-const qualified overloads and we don't want to restrict the user. As such, it is again the responsibility of the user to ensure the object isn't modified against the business logic.

This is a weird high order function, and can definitely be emulated with map. One possible use is to emulate dynamic dispatch among a bunch of functions or function objects that shouldn't be in a virtual dispatch inheritance hierarchy. This avoids a common design problem whereby the user is potentially forced to implement other pure virtual functions of a base class. There is also the nice property that the call is only made if it is type-safe to do so, and it is implied by the resulting variant. This means we don't have to find out at runtime that the function, say, throws some kind of not implemented exception.

template <typename Reduce> constexpr decltype(auto) reduce(Reduce &&r) const;
Folds the whole variant into some kind of other type.

This version of the high order function is not exactly the same as the common understanding of reduce or fold. There can only be one active member object, so the supplied reduction function cannot be called with all member types at once. The reduction function must only accept as an argument one object of each member type at a time.

Just like with map, there is no guarantee that the supplied reduction function will actually act on the active member object. Instead of returning an optional-wrapped variant, it returns an optional-wrapping of the common return type of the reduction function as if applied to all potential member types. The common return type is calculated as if by applying std::common_type_t to all of the reduction function's potential return type.

Unlike some of the other high order functions, the reduction function must accept any of the member types, and must return compatible types for all applications of the reduction function. Just like with map, if the reduction function does not handle a given member type, then it will be deduced as returning a something type, which likely does not have an underlying type in common with the reduction function's other return types.

Implementation notes

template <index_t MemberIndex, typename MemberType, typename... MemberTypes> template <typename CandidateType> constexpr variant_union<MemberIndex, MemberType, MemberTypes...>::variant_union( index<MemberIndex>, CandidateType &&that)

    : m_head(std::forward<CandidateType>(that))

See variant_union.

template <index_t MemberIndex, typename MemberType, typename... MemberTypes> template <index_t CandidateIndex, typename CandidateType> constexpr variant_union<MemberIndex, MemberType, MemberTypes...>::variant_union( index<CandidateIndex>, CandidateType &&that)

    : m_tail(index_v<CandidateIndex>, std::forward<CandidateType>(that)) {}

A union must have an active member object, so construction must happen in the initializer, and not in the body of the constructor.

This union should be as basic as possible, so the body of the constructor must be kept empty.

template <index_t MemberIndex, typename MemberType, typename... MemberTypes> template <index_t CandidateIndex, typename CandidateType> constexpr decltype(auto) variant_union<MemberIndex, MemberType, MemberTypes...>:: operator=(forwarding_ntuple<index<CandidateIndex>, CandidateType> that) {

  if constexpr (CandidateIndex > MemberIndex)
    m_tail = that;
  

This is a recursive assign, as this will call m_tail's assignment, which is this very templated function we are in.

We don't need to std::forward because forwarding_ntuple maintains the argument's reference category.

  else
    m_head = std::forward<CandidateType>(that.get(index_v<1>));
  

We assume that once it gets to this stage, the overarching variant has done all the necessary checks to be sure that the assigned to object is the active member object. We assume this is the case because the overarching variant's assignment protocol is that we use move assignment or copy assignment for the union if the variant is wholly trivially default assignable; or we use placement new if not, and if the active member object is not assignable from the the assigned from object. See variant::operator=.

template <typename ThatType> struct variant_match {

template <typename MemberType> using exact = std::is_same<std::conditional_t<std::is_reference_v<MemberType>, MemberType, remove_cvref_t<MemberType>>, std::conditional_t<std::is_reference_v<MemberType>, ThatType, remove_cvref_t<ThatType>>>;
Type filter to check whether the member type is exactly the same as another.

If a member type is a reference, then we really are saying we must only have references, so we must compare exactly with references intact. If the member type is not a reference, then the user supplying an object may not explicitly state that that is a reference and so an exact match with references stripped is okay.

In the future, allow for a const member type that underneath is not const because const members are annoying.

This is kind of like argument binding, but for templates.

template <typename MemberType> using construct = std::is_constructible<MemberType, ThatType>;
Type filter to check whether member type is constructible from another.

Don't need to do any clever reference matching stuff, since the trait already covers it.

This is kind of like argument binding, but for templates.

template <typename MemberType> using assign = std::is_assignable<remove_cvref_t<MemberType> &, ThatType>;
Type filter to check whether member type is assignable from another.

The member type may not be a reference, but if it is the active member object at the time of assignment, then it is a reference because it is named, so we must check types based on that reality.

This is kind of like argument binding, but for templates.

template <typename ThatType> template <typename... MemberTypes> constexpr auto variant_match<ThatType>::match_construct() {

  constexpr type_list list = make_type_list<MemberTypes...>();
  constexpr index exact_index = index_v<match_indices<exact>(list)[index_v<0>]>;
  

We're only interested in the first exact match to avoid ambiguity.

  [[maybe_unused]] constexpr index construct_index =
      index_v<match_indices<construct>(list)[index_v<0>]>;
  

We're only interested in the first constructible match to avoid ambiguity.

  if constexpr (exact_index > -1)
    return exact_index;
  else if constexpr (construct_index > -1)
    return construct_index;
  

template <typename ThatType> template <typename... MemberTypes> constexpr auto variant_match<ThatType>::match_assign() {

  constexpr type_list list = make_type_list<MemberTypes...>();
  constexpr index exact_index = index_v<match_indices<exact>(list)[index_v<0>]>;
  

We're only interested in the first exact match to avoid ambiguity.

  [[maybe_unused]] constexpr index assign_index =
      index_v<match_indices<assign>(list)[index_v<0>]>;
  

We're only interested in the first constructible match to avoid ambiguity.

  if constexpr (exact_index > -1)
    return exact_index;
  else if constexpr (assign_index > -1)
    

template <typename... MemberTypes> template <typename CandidateType, typename> constexpr variant<MemberTypes...>::variant(CandidateType &&that)

    : variant(variant_match<CandidateType>::template match_construct<
                  MemberTypes...>(),
              std::forward<CandidateType>(that)) {}

Unlike with operator=, we don't have to worry about accidental conversion issues, but we still prioritize an exact match for the member type over constructibility. Most people would expect that implicitly because if a member type that exactly matches is present, it is most likely because the user designed it that way. If they wanted different behaviour, they should use indexed construction.

See match_construct.

template <typename... MemberTypes> template <typename CandidateType, typename> constexpr decltype(auto) variant<MemberTypes...>:: operator=(CandidateType &&that) {

  variant::operator=
      ({variant_match<CandidateType>::template match_assign<MemberTypes...>(),
        std::forward<CandidateType>(that)});
  

Finds the index of the first assignable member type in this variant. We use match_assign's specified type matching priority instead of trying to find the active member object to check its assignability with the given object. The main reason is that I think the main expectation a user has is that, if a variant is assigned with a new object, they don't expect implicit conversions if the exact type is found. A variant assignment is used mostly so that the user can forget the detail about what it is active at the time. To prioritize assignment to the active member object would prioritize implicit conversions, while helpful in some cases, would mean every user of variant must keep track of whether it currently holds an active member object that is assignable from the given object.

It is much more predictable if the "fast-path" of assigning to the active member object is an opt-in optimization rather than the default behaviour, and that optimization can be triggered by the explicit indexed assignment instead, because using that asssignment means the user has opted in to having to keep track of the index, whether implicitly or explicitly.

template <typename... MemberTypes> template <index_t CandidateIndex, typename CandidateType> constexpr decltype(auto) variant<MemberTypes...>:: operator=(forwarding_ntuple<gut::index<CandidateIndex>, CandidateType> that) {

  if constexpr (std::is_trivially_copy_assignable_v<union_type> ||
                std::is_trivially_move_assignable_v<union_type>)
    

Do we need to check for trivial destructibility? Or is the check for trivial assignment good enough to imply that no destruction is necessary. This needs to be tested at runtime because non-trivial destructors aren't constexpr-friendly.

Or we can err on the defensive side and add an extra check for trivial destructibility.

I suppose that if a type is trivially assignable, but not trivially destructible, the onus is on the user to ensure that is the case. However, we can't be sure if they decided that it was by design, or that they didn't know about the Rule of Three/Five.

    m_union = union_type{that.get(index_v<0>),
                         std::forward<CandidateType>(that.get(index_v<1>))};
  

Turns out you can just assign a whole new union if it's trivial to do so, instead of faffing about with finding the active member object, and destroy it or check it for assignability.

What if construction+assignment is not as cheap?

  else if constexpr (std::is_assignable_v<
                         decltype(get_type<CandidateIndex>(
                             make_index_list<MemberTypes...>()))::raw_type,
                         CandidateType>) {
    if (index() == CandidateIndex)
      m_union = that;
    

Can't simply assign a new union like before, since in this branch, we know there must be at least one non-trivially assignable member, meaning we can't know at compile-time the active member object.

    else
      needsReset = true;
    

If the union is not trivially assignable, and the given index is not the active member index, despite the given object being assignable.

  } else
    needsReset = true;
  

If the union is not trivially assignable, and the given object is not assignable in any way.

  if (needsReset) {
    reset();
    new (std::addressof(m_union))
        union_type{index_v<CandidateIndex>,
                   std::forward<CandidateType>(that.get(index_v<1>))};
    

constexpr-ness is preserved as long as this execution path is never taken at compile-time, as both reset and placement new are not constexpr friendly.

template <typename... MemberTypes> template <typename MemberType> constexpr decltype(auto) variant<MemberTypes...>::value(type<MemberType>) const {

See value.

template <typename... MemberTypes> template <typename MemberType> constexpr decltype(auto) variant<MemberTypes...>::value(type<MemberType>) {

  using return_type =
      std::conditional_t<std::is_reference_v<MemberType>, MemberType,
                         remove_cvref_t<MemberType> &>;
  

Force the return type to be a reference.

  return reduce(*this, [](auto &&v) -> return_type {
    

It was a strange epiphany that lead to the insight that getting the value by type can be written in terms of a foldreduce. Unlike getting something by index, we have to find the active member object first. The hidden blessing in disguise is that if this variant has multiple types that can satisfy the requested type and the active member object is of one of those, then we can get at the value without caring about which variant or role of the active member object we are interested in.

Then, since we are only interested in one type, well, there's one high order operation that takes in a bunch of things and returns just one thing. In actual fact, value by index was implemented first, since that is what all algebraic types implement, and they normally leave it at that. It wasn't until adding value by type to ntuple, and then wanting to implement it for variant that I realized value was just a fold.

It may seem inefficient to implement value by type this way, but you'd actually have to search for the active member object in either case. The non-matching cases are inexpensive index/integer equality comparison followed by a no-op. I suppose you can also implement this in terms of filter, but that would involve unnecessarily creating a temporary single member variant first.

    if constexpr (std::is_convertible_v<decltype(v), return_type>)
      return static_cast<return_type>(v);
    

Is std::is_convertible_v what we want here? We must return a reference, and you can't convert a reference of one type to another. I suppose you can convert an r-value reference to an l-value reference, or a non-const reference to a const reference.

    else
      return optional<return_type>{}.value();
    

This is unsafe, but then, so is calling value without being absolutely sure the active member object satisfies the type. If the given type and the active member object is correct, then this branch would not be called now, would it? Also, we must satisfy the declared return type for this reduction function, and the resulting type for each member type must be the same, since it is a fold.

template <typename... MemberTypes> template <index_t MemberIndex> constexpr decltype(auto) variant<MemberTypes...>::get(gut::index<MemberIndex>) const {

See get

template <typename... MemberTypes> template <index_t MemberIndex> constexpr decltype(auto) variant<MemberTypes...>::get(gut::index<MemberIndex>) {

  if constexpr (MemberIndex == 0)
    return index();
  

Inspired by fm2gp - The Law of Useful Return.

template <typename... MemberTypes> constexpr void variant<MemberTypes...>::reset() {

reset is not a public member because a variant should always have an active object of a type in the member list.

It is also declared as constexpr, even though a destructor is potentially called, because it just simplifies things to be able to call this function in many contexts without having to check the type traits for each permutation of triviality.

  make_index_list<count()>().reduce([this](auto... i) mutable constexpr {
    

Can't loop over the index range because we lose constexpr-ness, so instead fold over the index constants.

    (..., [ this, i ]() mutable constexpr {
      

GCC handles parameter packs in a lambda capture in an expansion just fine, except in a nested lambda expansion?

      if (index() == i)
        m_union.reset(i);
      

So while the active member index is a runtime value, we can still infer the constexpr index from it, and our index shouldn't go out of synch with the compiler's own active member tracking.

Even though this looks like a loop, the underlying variant_union's reset will only be called once, since there can only be one active member object.

    }());
    

Immediately invoked lambda in a fold expression makes complex parameter pack handling so very easy.

template <typename... MemberTypes> template <typename Variant, typename Map> constexpr decltype(auto) variant<MemberTypes...>::map(Variant &&v, Map &&m) {

The public member function overloads defer to here, where we can handle the case of both a const and non-const variant generically. Aditionally, all high order functions for variant eventually wind up here - it is a very useful base operation to implement high order functions.

    using result_type = variant<return_type_t<Map, decltype(v.value(i))>...>;
    optional<result_type> result;
    

It's much easier to create the return object and then assign to it than to complicate the fold expression even more. I suspect it can't actually be done otherwise, since we can't know the active member object at compile time, and so we can't even use a future CTAD enabled version of variant.

You can see why we have to resolve the supplied mapping function's return type to something. The variant can't have void or no type members.

    (..., [&v, &m, &result, i ]() mutable constexpr->decltype(auto) {
      

GCC handles parameter packs in a lambda capture in an expansion just fine, except in a nested lambda expansion?

      if (v.index() == i)
        result = {i, call(m, v.value(i))};
      

Even though this looks like a loop, the assignment only happens once since there can only be one active member object at a time.

    }());
    

Immediately invoked lambda in a fold expression makes complex parameter pack handling so very easy.

template <typename... MemberTypes> template <typename Variant, typename Reduce> constexpr decltype(auto) variant<MemberTypes...>::reduce(Variant &&v, Reduce &&r) {

    using result_type =
        std::common_type_t<return_type_t<Reduce, decltype(v.value(i))>...>;
    optional<result_type> result;
    v.each([&result, &r ](auto &&v) mutable constexpr { result = call(r, v); });
    

We call the variant's each to be consistent with the other algebraic types, but the supplied reduction function is only called at most once. We used calldeferred invocation to signify that we expect the same return type deduction behaviour, even if it isn't strictly necessary.

Language support hooks

Structured binding tuple size

template <typename... MemberTypes>
class std::tuple_size<gut::variant<MemberTypes...>>
    : public std::integral_constant<gut::count_t, sizeof...(MemberTypes) + 1> {
  

The first object of the structured binding is actually the index of the active member object, hence the sizeof...(...) + 1.

Structured binding tuple element type

template <std::size_t MemberIndex, typename... MemberTypes>

Even though we use count_t internally, we must still usedstd::size_t, since GCC 8 complains about it if we don't match the template parameter type of std::tuple_element exactly.

class std::tuple_element<MemberIndex, gut::variant<MemberTypes...>> {
public:
  using type = std::conditional_t<
      MemberIndex == 0, gut::index_t,
      decltype(&gut::variant<MemberTypes...>{}.value(
          gut::index_v<static_cast<gut::index_t>(MemberIndex) - 1>))>;
  

Note the index - 1 if the element is not 0. If the element is 0, return the index of the active member object.

Compile time conformance tests

static_assert(sizeof(variant<something, something>) == 2);

Make empty member optimization so that sizeof(...) is 1. The rationale behind this is that if a member type is an empty type, it would be cheap to construct one if needed, instead of storing it. All that is needed to represent the choice is for the index to work as designed.