Skip to content

<xstd/misc/type_traits/empty_member_type.hpp>

xstd::empty_member_type

template<class Tag = void>
struct empty_member_type {
  empty_member_type() = default;
  template<class... Args>
    requires ((not same_as<remove_cvref_t<Args>, empty_member_type>) and ...)
    constexpr explicit empty_member_type(Args&&...) noexcept;
  friend auto operator<=>(empty_member_type, empty_member_type) -> strong_ordering = default;
};

A tagged empty class that keeps the place of a data member that is not there.

Template parameters

Parameter Meaning
Tag Distinguishes one empty member from another. Defaults to void, and distinct tags give distinct types.

Member functions

  • The variadic constructor ignores its arguments, so an enclosing class can construct the member as it would the type it stands in for.
    • Constraints: No Args is the class itself, so copying from a mutable lvalue stays the trivial copy rather than the variadic constructor, which would match that lvalue better.
  • operator<=>: every two of its values compare equal and equivalent.

Notes

  • It is an empty, regular, totally ordered, trivial class, whose special members do not throw, usable in constant expressions.
  • Its comparisons reach a class derived from it, and not a class that holds it as a member.

Example

#include <xstd/misc/type_traits/empty_member_type.hpp> // empty_member_type
#include <iostream>                                    // boolalpha, cout
#include <string>                                      // string
#include <type_traits>                                 // conditional_t

// An entry keeps a label only where Labelled holds; elsewhere an empty stand-in is built and compared in its place.
template<bool Labelled>
struct entry
{
        int value;
        std::conditional_t<Labelled, std::string, xstd::empty_member_type<std::string>> label;

        [[nodiscard]] entry(int v, char const* name)
                : value(v)
                , label(name)
        {}

        [[nodiscard]] friend auto operator==(entry const&, entry const&) -> bool = default;
};

auto main() -> int
{
        std::cout << std::boolalpha;
        std::cout << "labelled entries 1 \"one\" and 1 \"uno\" are equal: " << (entry<true>(1, "one") == entry<true>(1, "uno")) << '\n';
        std::cout << "unlabelled entries 1 \"one\" and 1 \"uno\" are equal: " << (entry<false>(1, "one") == entry<false>(1, "uno")) << '\n';
        std::cout << "unlabelled entries 1 \"one\" and 2 \"two\" are equal: " << (entry<false>(1, "one") == entry<false>(2, "two")) << '\n';
}

Output:

labelled entries 1 "one" and 1 "uno" are equal: false
unlabelled entries 1 "one" and 1 "uno" are equal: true
unlabelled entries 1 "one" and 2 "two" are equal: false

See also

Entity Relation
empty_base_type the same for a base class, without comparisons
conditional_data_member picks this type where a condition fails
XSTD_NO_UNIQUE_ADDRESS lets a member of this type take no storage