Introduction

stdx has several aims:

  • Where possible, to enable using parts of the C++ standard library in versions earlier than when they were introduced, and where standard library implementations are lagging behind the standard.

  • To provide a common interface across C++ versions such that preprocessor directives in application code are minimized.

  • To provide implementations of standard behaviour that are richer or more efficient — at either compile-time or runtime — than the standard versions.

  • To provide useful functionality that is missing from the C++ standard but arguably belongs at a base level.

Compiler and C++ version support

The following compilers are supported:

  • clang 14

  • clang 15

  • clang 16

  • clang 17

  • gcc 12

  • gcc 13

In general, stdx supports the C++17 standard; however some functionality is available only in 20 and later.

algorithm.hpp

algorithm.hpp provides an implementation of some algorithms.

for_each and for_each_n

stdx::for_each is similar to std::for_each, but variadic in its inputs.

template <typename InputIt, typename Operation, typename... InputItN>
constexpr auto for_each(InputIt first, InputIt last,
                        Operation op, InputItN... first_n) -> Operation;
Note
stdx::for_each is constexpr in C++20 and later, because it uses std::invoke.

stdx::for_each_n is just like stdx::for_each, but instead of taking two iterators to delimit the input range, it takes an iterator and size.

template <typename InputIt, typename Size, typename Operation,
          typename... InputItN>
constexpr auto for_each_n(InputIt first, Size n,
                          Operation op, InputItN... first_n) -> Operation;

transform and transform_n

stdx::transform is similar to std::transform, but variadic in its inputs.

template <typename InputIt, typename OutputIt, typename Operation, typename... InputItN>
constexpr auto transform(InputIt first, InputIt last, OutputIt d_first,
                         Operation op, InputItN... first_n)
    -> transform_result<OutputIt, InputIt, InputItN...>;

Without the variadic pack InputItN…​ here, a call site looks like regular unary std::transform. But with the addition of the pack, stdx::transform becomes more like std::ranges::views::zip_transform. It can transform using an n-ary function and n sequences.

Note
The return value is equivalent to a tuple<OutputIt, InputIt, InputItN…​>. In C++20 and later this is a stdx::tuple, in C++17 a std::tuple.
Note
stdx::transform is constexpr in C++20 and later, because it uses std::invoke.

stdx::transform_n is just like stdx::transform, but instead of taking two iterators to delimit the input range, it takes an iterator and size.

template <typename InputIt, typename Size, typename OutputIt, typename Operation,
          typename... InputItN>
constexpr auto transform(InputIt first, Size n, OutputIt d_first,
                         Operation op, InputItN... first_n)
    -> transform_result<OutputIt, InputIt, InputItN...>;

bit.hpp

bit.hpp provides an implementation that mirrors <bit>, but is constexpr in C++17. It is mostly based on intrinsics.

bit_mask

bit_mask is a function for constructing a bit mask between most-significant and least-significant bits.

constexpr auto x = stdx::bit_mask<std::uint8_t, 5, 3>();
static_assert(x == std::uint8_t{0b0011'1000});

// Lsb may be omitted (meaning it's 0)
constexpr auto y = stdx::bit_mask<std::uint8_t, 5>();
static_assert(y == std::uint8_t{0b0011'1111});

// omitting both Msb and Lsb means the entire range of the type
constexpr auto z = stdx::bit_mask<std::uint8_t>();
static_assert(z == std::uint8_t{0b1111'1111});

Msb and Lsb denote a closed (inclusive) range where Msb >= Lsb. The first template argument must be an unsigned integral type.

bit_mask is also available for use with "normal" value arguments rather than template arguments:

constexpr auto x = stdx::bit_mask<std::uint8_t>(5, 3);
static_assert(x == std::uint8_t{0b0011'1000});

// Lsb may be omitted (meaning it's 0)
constexpr auto y = stdx::bit_mask<std::uint8_t>(5);
static_assert(y == std::uint8_t{0b0011'1111});

bit_pack

bit_pack is a function for packing multiple unsigned integral values into a larger bit width value.

constexpr auto x = stdx::bit_pack<std::uint32_t>(0x12, 0x34, 0x56, 0x78);
static_assert(x == 0x1234'5678);
constexpr auto y = stdx::bit_pack<std::uint32_t>(0x1234, 0x5678);
static_assert(y == x);

bit_pack can be used:

  • to pack 2 std::uint8_t​s into a std::uint16_t

  • to pack 2 std::uint16_t​s into a std::uint32_t

  • to pack 2 std::uint32_t​s into a std::uint64_t

  • to pack 4 std::uint8_t​s into a std::uint32_t

  • to pack 4 std::uint16_t​s into a std::uint64_t

  • to pack 8 std::uint8_t​s into a std::uint64_t

The arguments are listed in order of significance, i.e. for the binary overloads, the first argument is the high bits, and the second argument the low bits.

bit_size

bit_size returns a std::size_t: the size of its type argument in bits. For unsigned integral types, this is equivalent to std::numeric_limits<T>::digits.

constexpr std::size_t x = stdx::bit_size<std::uint8_t>();
static_assert(x == 8);

smallest_uint

smallest_uint is a function template that selects the smallest unsigned integral type that will fit a compile-time value.

constexpr auto x = stdx::smallest_uint<42>();   // std::uint8_t{}
constexpr auto y = stdx::smallest_uint<1337>(); // std::uint16_t{}

// smallest_uint_t is the type of a call to smallest_uint
using T = stdx::smallest_uint_t<1337>; // std::uint16_t

to_le and to_be

to_le and to_be are variations on byteswap that convert unsigned integral types to little- or big-endian respectively. On a little-endian machine, to_le does nothing, and to_be is the equivalent of byteswap. On a big endian machine it is the other way around.

constexpr auto x = std::uint32_t{0x12'34'56'78};
constexpr auto y = stdx::to_be(x); // 0x78'56'34'12 (on a little-endian machine)

to_le and to_be are defined for unsigned integral types. Of course for std::uint8_t they do nothing.

bitset.hpp

bitset.hpp provides an implementation that mirrors std::bitset, but is constexpr in C++17 and has the following differences:

The underlying type can be specified: stdx::bitset<8, std::uint16_t> defines a bitset with 8 bits whose storage type is std::uint16_t. The storage type must be an unsigned integral type. It controls the value_type of the underlying storage and hence may affect efficiency for some operations according to platform.

  • Stream input and output operators are not implemented.

  • A std::hash specialization is not implemented.

  • to_string, to_ulong and to_ullong are not implemented

A bitset has two template parameters: the size of the bitset and the storage element type to use. The storage element type must be unsigned.

template <std::size_t N, typename Element = void>
struct bitset;

If the storage element type is omitted, the smallest unsigned type that will fit the size is selected, or std::uint64_t if the size is more than 64.

using A = stdx::bitset<8>;   // uses uint8_t
using B = stdx::bitset<16>;  // uses uint16_t
using C = stdx::bitset<32>;  // uses uint32_t
using D = stdx::bitset<64>;  // uses uint64_t
using E = stdx::bitset<128>; // uses (array of) uint64_t
using F = stdx::bitset<128, std::uint8_t>; // uses (array of) uint8_t

A bitset can be created from a std::uint64_t:

auto bs = stdx::bitset<8>{0b1100}; // bits 2 and 3 set

Or by specifying which bits are set and using stdx::place_bits:

auto bs = stdx::bitset<8>{stdx::place_bits, 2, 3}; // bits 2 and 3 set

Or with a string_view (potentially by substring and with a known value for set bits):

using namespace std::string_view_literals;
auto bs1 = stdx::bitset<8>{"1100"sv};            // bits 2 and 3 set
auto bs2 = stdx::bitset<8>{"1100"sv, 0, 2};      // bits 0 and 1 set
auto bs3 = stdx::bitset<8>{"AABB"sv, 0, 2, 'A'}; // bits 0 and 1 set

To convert a bitset back to an integral type, to<T> is available where T is an unsigned integral type large enough to fit all the bits. And to_natural produces the smallest such unsigned integral type.

auto bs = stdx::bitset<11>{0b101}; // 11 bits, value 5
auto i = bs.to<std::uint64_t>();   // 5 (a std::uint64_t)
auto j = bs.to_natural();          // 5 (a std::uint16_t)

cached.hpp

A cached value represents a value that is computed on demand once, and cached thereafter. It is constructed with a lambda expression that will compute the value when needed.

constexpr auto c = stdx::cached{[] { return expensive_computation(); }};

A cached value is something like a std::optional and supports some similar functionality. Note though that any kind of "dereference" operation automatically computes the value if needed.

// check whether the value is present
auto b = c.has_value();

// or, automatic bool conversion (explicit)
if (c) {
  // do something
}

// use the value (computing where necessary)
auto value = *c;
auto alt_value = c.value();
auto value_member = c->member;

// reset the value
c.reset();

reset means that the next time the value is needed, it will be recomputed. However, refresh immediately recomputes the value.

// immediate recomputation
c.refresh();

If needed, the type of the cached value can obtained with cached_value_t.

auto c = stdx::cached{[] { return expensive_computation(); }};
using V = stdx::cached_value_t<decltype(c)>;
Note
You can also use typename decltype(c)::value_type, but if the type of c has cvref qualifiers, cached_value_t saves the bother of using remove_cvref_t.

compiler.hpp

compiler.hpp provides macros for decorating declarations, which resolve either to keywords or to compiler-specific attributes:

concepts.hpp

concepts.hpp implements various standard concepts. In C++17 they are surfaced as compile-time boolean values.

Note
For compatibility with the standard and with std::is_base_of, a class is considered to be derived_from itself.
Note
range is in the stdx namespace, and is defined in terms of std::begin and std::end, unlike the standard which has std::ranges::range defined in terms of std::ranges::begin and std::ranges::end.

concepts.hpp also has a couple of non-standard but useful concepts.

callable

callable is modelled by functions by and objects with operator(). In particular it is true for generic lambda expressions, where operator() is a function template.

auto f() -> void {}
static_assert(stdx::callable<decltype(f)>);

auto lambda = [] (int i) { return i + 1; };
static_assert(stdx::callable<decltype(lambda)>);

auto generic_lambda = [] (auto i) { return i + 1; };
static_assert(stdx::callable<decltype(generic_lambda)>);

has_trait

has_trait is used to turn a type trait (standard or otherwise) into a concept. There are many type traits and comparatively few standard concepts; this concept helps bridge the gap more easily and concisely than writing out boilerplate concepts for type traits.

template <stdx::has_trait<std::is_class> T>
auto f(T) -> void {
  // can only be called with class types
}

For the purposes of has_trait, a type trait is a class template with one parameter that has a constexpr static bool value member.

same_as_unqualified

same_as_unqualified is true when two types are the same are removing top-level cv-qualifications and references, if any. It’s useful for constraining hidden friends — particularly when member functions would need to be replicated with different reference qualifiers before C++23.

struct S {
  // before C++20
  auto f() & { /* ... */ }
  auto f() const & { /* ... */ }
  auto f() && { /* ... */ }
  auto f() const && { /* ... */ }
  // (and 4 more for volatile qualifiers!)

  // with C++23's explicit object parameter ("deducing this")
  template <typename Self>
  auto f(this Self&& s) {
    // Self is perfectly-forwarded
  }

private:
  // hidden friend alternative
  template <same_as_unqualified<S> Self>
  friend auto f(Self&& s) {
    // Self is perfectly-forwarded
  }
};

ct_conversions.hpp

ct_conversions.hpp provides two compile-time functions for obtaining strings of type names and enumerator names.

template <typename T>
consteval auto type_as_string() -> std::string_view;

template <auto E>
consteval auto enum_as_string() -> std::string_view;

ct_string.hpp

A ct_string is a compile-time string that can be used as a non-type template parameter (NTTP).

Note
ct_string is available only in C++20 and later. It requires compiler support for using structural types as NTTPs.

Example usage:

template <ct_string S>
struct named_thing { ... };

auto my_thing = named_thing<"mine">{};

Here we declare a struct with an NTTP, and instantiate the template with a string. When compiled, "mine" will create a ct_string which is the NTTP passed to named_thing.

Note
ct_string is a class template. The declaration of named_thing here uses ct_string as a placeholder type for an NTTP, whose concrete type will be deduced. This is new for C++20 - see https://en.cppreference.com/w/cpp/language/template_parameters for details.

The ct_string interface:

template <ct_string S>
struct named_thing {
  template <ct_string Other>
  auto f() {
    // here we can:
    constexpr std::size_t sz = S.size();  // ask for ct_string's size
    constexpr bool is_empty = S.empty();  // ask whether a ct_string is empty
    constexpr bool equal = S == Other;    // compare two ct_strings

    // we can also convert to/from cib string constants
    constexpr auto cib_sc_string = stdx::ct_string_to_type<S, sc::string_constant>();
    constexpr auto stdx_ct_string = stdx::ct_string_from_type(cib_sc_string);
    static_assert(S == stdx_ct_string);

    // we can make a ct_string with a UDL:
    using namespace stdx::ct_string_literals;
    constexpr auto s = "Hello"_cts;

    // we can concatenate two ct_strings:
    constexpr auto s1 = "abc"_cts;
    constexpr auto s2 = "def"_cts;
    constexpr auto s3 = s1 + s2; // abcdef

    // and we can split a ct_string at the first occurrence of a character,
    // optaining a pair of ct_strings
    constexpr auto p = stdx::split<S, '/'>();
    // if the character doesn't exist, p.first is equal to S and p.second is empty
    // otherwise p.first is everything up to (but not including) the character,
    // and p.second is everything after (also not including)

    // we can also iterate over a ct_string
    for (auto c : S) { ... }

    // a ct_string can also explicitly convert to a std::string_view
    constexpr auto sv = static_cast<std::string_view>(S);

    // when required, we can access the underlying array of chars,
    // which includes the null terminator
    const auto& char_array = S.value;
  }
};
Note
size and empty are always available as constexpr.
Note
_cts is a user-defined literal declared in stdx::literals::ct_string_literals, where both literals and ct_string_literals are inline namespaces.
Caution
ct_string stores an internal array (value) which includes the null terminator for the string. The size reported by the ct_string is one less than the size of the internal array. Thus the begin, end and size of a ct_string all represent the string of characters without the null terminator. However, for interfacing with legacy functions, a null terminator can be useful.

See cib documentation for details about the cib string constant class.

cx_map.hpp

cx_map implements a constexpr-capable map with a compile-time capacity. The map is unordered.

template <typename Key,
          typename Value,
          std::size_t N>
class cx_map;

The cx_map interface:

template <typename K, typename V, std::size_t N>
auto f(stdx::cx_map<K, V, N> m) {
    // here we can:
    std::size_t sz = m.size(); // ask for m's size
    constexpr std::size_t cap = m.capacity(); // ask for m's capacity (same as N)
    bool is_empty = m.empty(); // ask whether a cx_map is empty
    bool is_full = m.full(); // ask whether a cx_map is full
    m.clear() // clear a cx_map

    // we can use some of the usual functions
    m.insert_or_assign(K{}, V{});
    m.put(K{}, V{}); // same as insert_or_assign
    V& v = m.get(K{});
    bool c = m.contains(K{});
    m.erase(K{});

    // and use iterators:
    // begin and end
    // cbegin and cend
    // (therefore also range-for loops)

    // we can also pop an arbitrary element
    auto [k, v] = m.pop_back();
};

insert_or_assign returns true if a value was inserted, false if it was assigned.

Note
capacity is always available as constexpr, even though m above is a function parameter and therefore not constexpr.

cx_multimap.hpp

cx_multimap implements a constexpr-capable multimap with a compile-time capacity. The multimap is unordered. As well as a key-capacity, each key has a limited number of values that can be associated with it (the value-capacity).

template <typename Key,
          typename Value,
          std::size_t KeyN,
          std::size_t ValueN = KeyN>
class cx_multimap;

The cx_multimap interface:

template <typename K, typename V, std::size_t KN, std::size_t VN>
auto f(stdx::cx_map<K, V, KN, VN> m) {
    // here we can:
    std::size_t sz = m.size(); // ask for m's size
    constexpr std::size_t cap = m.capacity(); // ask for m's capacity (same as KN)
    bool is_empty = m.empty(); // ask whether a cx_multimap is empty
    m.clear() // clear a cx_multimap

    m.insert(K{}); // make sure a key exists
    m.insert(K{}, V{}); // associate a value with a key
    m.put(K{}); // same as insert
    m.put(K{}, V{}); // same as insert

    auto& v = m.get(K{}); // v is a cx_set<V, VN>
    bool c1 = m.contains(K{});
    bool c2 = m.contains(K{}, V{});
    m.erase(K{});
    m.erase(K{}, V{});

    // and use iterators:
    // begin and end
    // cbegin and cend
    // (therefore also range-for loops)
};
Note
capacity is always available as constexpr, even though m above is a function parameter and therefore not constexpr.

cx_queue.hpp

cx_queue is a circular queue with a compile-time capacity and a policy that controls whether and how over/underflow is handled.

template <typename T, std::size_t N,
          typename OverflowPolicy = safe_overflow_policy>
class cx_queue;
Note
By default, cx_queue uses the safe_overflow_policy which means that over/underflow will result in a call to panic. unsafe_overflow_policy (which does no checking) is also available.

The cx_queue interface:

template <typename T, std::size_t N, typename P>
auto f(stdx::cx_queue<T, N, P> q) {
    // here we can:
    std::size_t sz = q.size(); // ask for q's size
    constexpr std::size_t cap = q.capacity(); // ask for q's capacity (same as N)
    bool is_empty = q.empty(); // ask whether a cx_queue is empty
    bool is_full = q.full(); // ask whether a cx_queue is full
    q.clear(); // clear a cx_queue

    // we can use some of the usual queue functions
    q.push(T{});
    T& t1 = q.front();
    T& t2 = q.back();
    T t = q.pop();
  }
};
Note
capacity is always available as constexpr, even though q above is a function parameter and therefore not constexpr.

Users of cx_queue may provide custom overflow policies. A policy must implement two (static) functions:

struct custom_overflow_policy {
    constexpr static auto check_push(std::size_t size, std::size_t capacity) -> void {
      // push is safe if size < capacity
      // otherwise, overflow panic
    }
    constexpr static auto check_pop(std::size_t size) -> void {
      // pop is safe if size > 0
      // otherwise, underflow panic
    }
};

cx_set.hpp

cx_set implements a constexpr-capable set with a compile-time capacity. The set is unordered.

template <typename Key, std::size_t N>
class cx_set;

The cx_set interface:

template <typename K, std::size_t N>
auto f(stdx::cx_set<K, N> s) {
    // here we can:
    std::size_t sz = s.size(); // ask for s's size
    constexpr std::size_t cap = s.capacity(); // ask for s's capacity (same as N)
    bool is_empty = m.empty(); // ask whether a cx_set is empty
    m.clear() // clear a cx_set

    // we can use some of the usual functions
    s.insert(K{});
    bool c = s.contains(K{});
    s.erase(K{});

    // and use iterators:
    // begin and end
    // cbegin and cend
    // (therefore also range-for loops)

    // we can also pop an arbitrary element
    auto k = s.pop_back();

    // and merge two sets (assuming s has capacity)
    s.merge(cx_set{1, 2, 3});
};

insert returns true if a value was inserted, false if it already existed.

Caution
When merging one set into another, the destination set must have enough capacity!
Note
capacity is always available as constexpr, even though s above is a function parameter and therefore not constexpr.

A cx_set may also be initialized with CTAD:

// s is a cx_set<int, 3>
auto s = cx_set{1, 2, 3};

cx_vector.hpp

cx_vector is a contiguous data structure with a compile-time capacity and variable (but bounded) size. Basically parts of the std::vector interface applied to a std::array.

template <typename T,
          std::size_t N>
class cx_vector;

The cx_vector interface:

template <typename T, std::size_t N>
auto f(stdx::cx_vector<T, N> v) {
    // here we can:
    std::size_t sz = v.size(); // ask for v's size
    constexpr std::size_t cap = v.capacity(); // ask for v's capacity (same as N)
    bool is_empty = v.empty(); // ask whether a cx_vector is empty
    bool is_full = v.full(); // ask whether a cx_vector is full
    bool equal = v == v; // compare two cx_vectors (of the same type)
    v.clear() // clear a cx_vector

    // we can use some of the usual functions
    v.push_back(T{});
    T& t1 = v[0];
    T& t2 = v.back();
    T t = v.pop_back();

    // and use iterators:
    // begin and end
    // cbegin and cend
    // rbegin and rend
    // crbegin and crend
    // (therefore also range-for loops)

    // we can have compile-time access with get
    T& t3 = get<0>(v);

    // and we can use resize_and_overwrite for efficient initialization
    resize_and_overwrite(v,
      [] (T* data, std::size_t sz) -> std::size_t {
        // copy Ts to data and return the new size
        return 0;
      });
  }
};
Note
capacity is always available as constexpr, even though v above is a function parameter and therefore not constexpr.

A cx_vector may also be initialized with CTAD:

// v is a cx_vector<int, 3>
auto v = cx_vector{1, 2, 3};

for_each_n_args.hpp

for_each_n_args.hpp provides a method for calling a function (or other callable) with batches of arguments from a parameter pack.

Examples:

auto f(int x, int y) -> void { /* do something with x and y */ }
stdx::for_each_n_args(f, 1, 2, 3, 4); // this calls f(1, 2) and f(3, 4)

The number of arguments passed to for_each_n_args must be a multiple of the argument "batch size" - which by default is the arity of the passed function.

Sometimes, the passed callable is a generic function where the arity cannot be automatically determined, or sometimes it may be a function with default arguments which we want to use. In that case it is possible to override the default batch size:

auto f(auto x, auto y) -> void { /* do something with x and y */ }
stdx::for_each_n_args<2>(f, 1, 2, 3, 4); // this calls f(1, 2) and f(3, 4)

auto g(int x, int y, int z = 42) -> void { /* do something with x, y and z */ }
stdx::for_each_n_args<2>(g, 1, 2, 3, 4); // this calls g(1, 2, 42) and g(3, 4, 42)

function_traits.hpp

function_traits.hpp contains type traits for introspecting function signatures. It works with functions, lambda expressions, and classes with operator().

Examples:

auto f1() -> void {}
using f1_return = stdx::return_t<decltype(f1)>;         // void
using f1_args = stdx::args_t<decltype(f1), std::tuple>; // std::tuple<>

auto f2(int) -> int { return 0; }
using f2_return = stdx::return_t<decltype(f2)>;         // int
using f2_args = stdx::args_t<decltype(f2), std::tuple>; // std::tuple<int>

auto l = [] (int) -> int { return 0; };
using l_return = stdx::return_t<decltype(l)>;         // int
using l_args = stdx::args_t<decltype(l), std::tuple>; // std::tuple<int>

stdx::args_t returns a list of the function arguments. std::decayed_args_t returns the same list, but with std::decay_t applied to each element. This is useful for example when you need to copy and store a tuple of the arguments.

auto f(int&, std::string&) -> void {}
using f_args = stdx::decayed_args_t<decltype(f), std::tuple>; // std::tuple<int, std::string>

stdx::arity_t returns the arity of a function (as a std::integral_constant). stdx::arity_v provides convenient access to the value.

auto f(int&, std::string&) -> void {}
using f_arity_t = stdx::arity_t<decltype(f)>; // std::integral_constant<std::size_t, 2>
constexpr auto f_arity_v = stdx::arity_v<decltype(f)>;

stdx::nth_arg_t returns a function parameter type at a given index. stdx::decayed_nth_arg_t is the equivalent with std::decay_t applied.

auto f(int&) -> void {}
using first_arg_t = stdx::nth_arg_t<decltype(f), 0>; // int&
using first_decayed_arg_t = stdx::decayed_nth_arg_t<decltype(f), 0>; // int
Note
Function traits work on functions (and function objects): not function templates or overload sets. For instance therefore, they will not work on generic lambda expressions.

The one exception to this is stdx::arity_t - it works on generic lambdas.

auto l = [](auto, auto) -> void {};
using f_arity_t = stdx::arity_t<decltype(l)>; // std::integral_constant<std::size_t, 2>
constexpr auto f_arity_v = stdx::arity_v<decltype(l)>;

functional.hpp

Note
P2714 added the ability (in C++26) to use a non-type template parameter for the bound function; this works for function pointers in C++17, and also for lambda expressions in C++20 and beyond.

with_result_of

with_result_of is a class that can be used for lazy evaluation. with_result_of wraps a callable (often a lambda expression) and can implicitly convert to the return type of the callable. It may be passed to functions that perfectly forward their arguments - a good example is an emplace function on a container - and the conversion happens only when the required value is actually used.

// S is a type that is some work to construct
// so we use a maker function
struct S { ... };
auto make_S() -> S;

std::unordered_map<int, S> m;

v.emplace(0, make_S()); // this works, but incurs a temporary construct, move and destruct
v.emplace(0, stdx::with_result_of{make_S}); // this constructs S in-place thanks to RVO

with_result_of can help to achieve in-place construction, effectively by deferring evaluation of function arguments.

intrusive_forward_list.hpp

intrusive_forward_list is a singly-linked list designed for use at compile-time or with static objects. It supports pushing and popping at the front or back, and removal from the middle.

// A node in an intrusive_list must have a next pointer
struct node {
  node *next{};
};

stdx::intrusive_forward_list<node> l;

node n1{};
l.push_front(&n1);

node n2{};
l.push_back(&n2);

node n3{};
l.push_back(&n3);

node* nf = l.pop_front();

l.clear();
bool b = l.empty();

intrusive_forward_list supports the same node validation policy arguments as intrusive_list.

intrusive_list.hpp

intrusive_list is a doubly-linked list designed for use at compile-time or with static objects. It supports pushing and popping at the front or back, forward iteration, and insertion and removal from the middle.

// A node in an intrusive_list must have prev and next pointers
struct node {
  node *prev{};
  node *next{};
};

stdx::intrusive_list<node> l;

node n1{};
l.push_front(&n1);

node n2{};
l.push_back(&n2);

node n3{};
auto i = std::find_if(std::begin(l), std::end(l), /* some predicate */);
l.insert(i, &n3); // insertion into the middle is constant-time

l.remove(&n2); // removal from the middle is constant-time
node* nf = l.pop_front();
node* nb = l.pop_back();

l.clear();
bool b = l.empty();

Node validity checking

An intrusive_list has a second template parameter which is whether to operate with or without node validity checking. The default is stdx::node_policy::checked.

// The second template argument here is the default
stdx::intrusive_list<node, stdx::node_policy::checked> l;

This means that:

  • nodes to be inserted/pushed must have prev and next pointers equal to nullptr

  • node prev and next pointers are cleared on removal/pop

Note
The second item here means that clear is a linear-time operation with stdx::node_policy::checked. For faster but less safe operation, stdx::node_policy::unchecked is available.

If the validity policy is violated (for example by attempting to push a node whose pointers are already populated) the result is a panic.

iterator.hpp

iterator.hpp contains ct_capacity, a constexpr function that returns the capacity of a container which is known at compile-time.

auto const a = std::array{1, 2, 3, 4};
constexpr auto c = stdx::ct_capacity(a); // std::size_t{4}

ct_capacity can be called with:

  • std::array

  • std::span (unless it has std::dynamic_extent)

  • stdx::span (unless it has stdx::dynamic_extent)

  • stdx::cx_map

  • stdx::cx_multimap

  • stdx::cx_queue

  • stdx::cx_set

  • stdx::cx_vector

ct_capacity_v is a corresponding variable template of type std::size_t.

memory.hpp

memory.hpp contains one thing:

numeric.hpp

numeric.hpp provides an implementation of some numeric algorithms.

transform_reduce and transform_reduce_n

stdx::transform_reduce is similar to std::transform_reduce, but variadic in its inputs.

template <typename T, typename InputIt, typename ROp, typename TOp,
          typename... InputItN>
constexpr auto transform_reduce(InputIt first, InputIt last, T init,
                                ROp rop, TOp top, InputItN... first_n) -> T;

stdx::transform_reduce is to std::transform_reduce as stdx::transform is to std::transform.

Note
The return type of stdx::transform_reduce is the first template parameter, which allows it to be manifestly fixed more conveniently to avoid a common pitfall with accumulation algorithms, viz. summing a range of double​s and passing 0 - an int - as the initial value.
Caution
Unlike stdx::transform, the return type here does not include the iterators: this means stdx::transform_reduce violates the Law of Useful Return (just like std::transform_reduce). However, it is probably true that stdx::transform_reduce is mostly used just for the accumulation result, where stdx::transform writes to an output iterator.
Note
Like stdx::transform, stdx::transform_reduce is constexpr in C++20 and later, because it uses std::invoke.

stdx::transform_reduce_n is just like stdx::transform_reduce, but instead of taking two iterators to delimit the input range, it takes an iterator and size.

template <typename T, typename InputIt, typename Size, typename ROp,
          typename TOp, typename... InputItN>
constexpr auto transform_reduce_n(InputIt first, Size n, T init, ROp rop,
                                  TOp top, InputItN... first_n) -> T;

optional.hpp

optional.hpp provides an implementation that mirrors <optional>, but uses tombstone_traits instead of a bool variable.

Here is a problem with std::optional:

enum struct S1 : std::uint8_t { /* ... values ... */ };
static_assert(sizeof(S1) == 1);
static_assert(sizeof(std::optional<S1> == 2);

enum struct S2 : std::uint16_t { /* ... values ... */ };
static_assert(sizeof(S2) == 2);
static_assert(sizeof(std::optional<S2>) == 4);

enum struct S3 : std::uint32_t { /* ... values ... */ };
static_assert(sizeof(S3) == 4);
static_assert(sizeof(std::optional<S3>) == 8);

std::optional basically stores a bool flag as well as the type. Because of size and alignment constraints, std::optional of a small type typically ends up being double the size of the type. Much of the time, we aren’t using all the bits in the type - especially if it’s an enumeration - so there is probably some sentinel value that we can treat as "invalid". AKA a "tombstone".

This is where tombstone_traits come in: specializing stdx::tombstone_traits allows us to specify that sentinel value and avoid storing an extra bool.

Note
The name "tombstone" arises from use in hash maps where it is used to signal a "dead" object.
enum struct S : std::uint8_t { /* ... values ... */ };

template <> struct stdx::tombstone_traits<S> {
    // "-1" is not a valid value
    constexpr auto operator()() const {
        return static_cast<S>(std::uint8_t{0xffu});
    }
};

static_assert(sizeof(S) == 1);
static_assert(sizeof(stdx::optional<S> == 1);

To use stdx::optional, specialize stdx::tombstone_traits for the required type, giving it a call operator that returns the sentinel value. After that, stdx::optional’s interface mirrors that of `std::optional. The C+​+23 monadic operations on std::optional are available on stdx::optional with C++17.

Why a call operator and not just a static value? To deal with move-only (and even non-movable) types.

Note
Like std::optional, stdx::optional can be constructed with std::nullopt or with std::in_place. (stdx does not redefine these types.)
Note
stdx::optional does not use exceptions. There is no stdx::bad_optional_access. If you access a disengaged stdx::optional, you will get the tombstone value!

Tombstone values

Instead of specializing stdx::tombstone_traits, sometimes it’s easier (especially for integral or enumeration types) to provide the tombstone value inline. We can do this with stdx::tombstone_value.

auto o = stdx::optional<int, stdx::tombstone_value<-1>>{};
Caution
Don’t specialize tombstone_traits for the builtin integral types - that’s risky if the definition is seen more widely. Instead use a stdx::tombstone_value where needed.
Note
The default tombstone_traits for floating-point types have infinity as the tombstone value. At first thought, NaN is the obvious tombstone, but NaNs never compare equal to anything, not even themselves.

Multi-argument transform

stdx::optional provides one extra feature over std::optional: the ability to call transform with multiple arguments. C++23 transform is a member function on stdx::optional too, but stdx::transform exists also as a free function on stdx::optional.

// S is a struct with tombstone_traits that contains an integer value

auto opt1 = stdx::optional<S>{17};
auto opt2 = stdx::optional<S>{42};
auto opt_sum = transform(
    [](S const &x, S const &y) { return S{x.value + y.value}; },
    opt1, opt2);

This flavor of transform returns the result of the function only if all of its stdx::optional arguments are engaged. If any one is not, a disengaged stdx::optional is returned.

panic.hpp

panic("reason") is a function that is used in emergencies: something fundamental went wrong (e.g. a precondition was violated) and there is no good way to recover. It’s like assert except that the behaviour of panic can be overridden.

A panic_handler is a struct that exposes a static panic method. That method may have two overloads: one that takes a ct_string template argument (as well as more runtime arguments), and another that takes only runtime arguments.

The default panic handler does nothing; to override that behaviour, provide a custom panic handler and specialize the variable template stdx::panic_handler, like this for example:

struct custom_panic_handler {
  static auto panic(auto&&... args) noexcept -> void {
    // log args and then...
    std::terminate();
  }

template <stdx::ct_string S>
  static auto panic(auto&&... args) noexcept -> void {
    // log args (including the compile-time string) and then...
    std::terminate();
  }
};

template <> inline auto stdx::panic_handler<> = custom_panic_handler{};

When something inside stdx goes wrong, the panic handler’s panic function will be called.

Note
stdx will always call panic with a compile-time string if possible (C++20), or with a single char const * if not. You are free to use panic with a logging framework to provide a "fatal" log function; in that case any arguments you pass through will be passed to panic and presumably handled by your choice of logging.

priority.hpp

priority_t<N> is a class that can be used for easily selecting complex function overloads. priority_t<0> is the lowest priority. priority<N> is a value of type priority_t<N>.

template </*some strong constraint*/ T>
auto f(T t, stdx::priority_t<2>) {
  // highest priority: call this function if possible
}

template </*some weaker/less preferred constraint*/ T>
auto f(T t, stdx::priority_t<1>) {
  // call this function if the highest-priority overload can't be called
}

template <typename /*no constraint*/ T>
auto f(T t, stdx::priority_t<0>) {
  // fallback to this function if both higher priority overloads don't fit
}

// at the call site, use the highest priority
auto result = f(t, stdx::priority<2>);

span.hpp

span.hpp provides an implementation of span.

tuple.hpp

tuple.hpp provides a tuple implementation that mirrors std::tuple. Mostly, stdx::tuple works the same way as std::tuple, with some extra functonality and faster compilation times. All functions on tuples are constexpr-capable.

Note
tuple is available only in C++20 and later.

Element types and size of a tuple

As with std::tuple, we can ask for the compile-time elements and size of a stdx::tuple using tuple_element_t and tuple_size_v.

template <typename T>
constexpr auto tuple_size_v = /* implementation */;
template <std::size_t I, typename T>
using tuple_element_t = /* implementation */;

The standard provides std::tuple_element and std::tuple_size as types; in stdx, only tuple_element_t and tuple_size_v are provided, since they are the most frequently used constructs. If needed, types can be synthesized from them, but if not, it’s quicker to compile just an alias and a variable template.

Constructing a tuple

A tuple can be constructed either with CTAD, or with make_tuple, or with forward_as_tuple.

auto t = stdx::tuple{1, 2};            // tuple<int, int>
auto t = stdx::make_tuple(1, 2);       // the same
auto t = stdx::forward_as_tuple(1, 2); // tuple<int&&, int&&>

make_tuple decays its arguments, just like std::make_tuple. However, std::make_tuple also does std::reference_wrapper-unwrapping, which stdx::make_tuple does not.

forward_as_tuple creates a tuple of forwarded references, like std::forward_as_tuple.

Accessing tuple elements

The ordinary way to access tuple elements is to use get, as with a std::tuple:

auto t = stdx::tuple{1, true}; // tuple<int, bool>
auto x_by_index = get<0>(t); // int
auto y_by_index = get<1>(t); // bool
auto x_by_type = get<int>(t); // int
auto y_by_type = get<bool>(t); // bool
Note
stdx::get is accessed here by argument-dependent lookup.

Like std::get, stdx::get with a type will be ambiguous if that type is in the tuple multiple times.

Unlike std::get, stdx::get is "SFINAE-friendly". If stdx::get causes a function template instantiation to be ill-formed, it will not cause an error, but instead will cause that function to be silently removed from the overload set candidates. With stdx::get, Substitution Failure Is Not An Error.

For access by index, we can also use indexing syntax:

using namespace stdx::literals;
auto t = stdx::tuple{1, true};
auto x_by_index1 = t[index<0>];
auto x_by_index2 = t[0_idx]; // equivalent

_idx is a user-defined literal in the stdx::literals namespace. It is equivalent to using the index variable template.

get is also available as a member function on stdx::tuple and works either by index or by type (using the tag variable template):

using namespace stdx::literals;
auto t = stdx::tuple{1, true};
auto x_by_index1 = t.get(index<0>);
auto x_by_index2 = t.get(0_idx);
auto x_by_type = t.get(tag<int>);
Note
All forms of access preserve the value category of the tuple; i.e. accessing an int member of a stdx::tuple const & gives an int const & and so on.

Comparing tuples

Comparing one stdx::tuple with another works the same way as with std::tuple.

Member functions on a tuple

apply can be used like std::apply, but is a member function.

auto t = stdx::tuple{1, 2};
auto sum = t.apply([] (auto... args) { return (args + ... + 0); }); // 3

apply is also available as a free function in tuple_algorithms.hpp.

fold_left and fold_right can be used to fold over a tuple and compute a reduction. They both take an initial value for the fold as the first argument, and a binary reduction function as the second.

auto t = stdx::tuple{1, 2, 3};
auto sum1 = t.fold_left(0, std::plus{});  // 6
auto sum2 = t.fold_right(0, std::plus{}); // also 6

Here, fold_left computes a left-fold, i.e. (​(​(0 + 1) + 2) + 3). fold_right computes the right-fold (0 + (1 + (2 + 3))).

Both forms of fold can perform computations in type-space as well as value-space. That is, the return type of the binary function passed to the fold can depend on the arguments. In this way a fold can build up a type dependent on what is in the tuple.

auto t1 = stdx::tuple{1, 2, 3};
auto t2 = t.fold_left(
  stdx::tuple{},
  [] (auto so_far, auto y) { return tuple_cat(so_far, stdx::tuple{y}); });
// t1 and t2 are the same, but intermediate types in the fold varied
Note
tuple_cat is an algorithm in tuple_algorithms.hpp.

join is a member function that works the same way as fold_left, but without needing an initial value. It is only defined for non-empty tuples.

auto t = stdx::tuple{1, 2, 3};
auto sum = t.join(std::plus{});  // 6

Indexed tuples

Sometimes, it is useful to index a tuple by something other than a plain std::size_t or a type. A tuple can act as a sort of map by indexing on multiple types, for instance. That’s the job of indexed_tuple.

template <typename Key, typename Value> struct map_entry {
  using key_t = Key;
  using value_t = Value;
  value_t value;
};
template <typename T> using key_for = typename T::key_t;

struct X;
struct Y;
auto t = stdx::make_indexed_tuple<key_for>(map_entry<X, int>{42},
                                           map_entry<Y, int>{17});
auto x = get<X>(t).value; // 42
auto y = get<Y>(t).value; // 17

Notice a few things here:

  • X and Y are tag types; declared only and not defined.

  • make_indexed_tuple takes a number of type functions (here just key_for) that define how to look up elements.

  • get is working not with a std::size_t index or the actual type contained within the tuple, but with the tag type that will be found by key_for.

A regular (unindexed) tuple can be converted to an indexed_tuple using apply_indices to add type-indexing functions:

// with definitions as above
auto t = stdx::tuple{map_entry<X, int>{42}}; // regular tuple
auto i = stdx::apply_indices<key_for>(t);    // tuple indexed with key_for
auto x = get<X>(i).value;                    // 42

tuple_algorithms.hpp

tuple_algorithms.hpp contains various (free function) algorithms that work on stdx::tuple.

Summary of tuple algorithms

  • all_of, any_of, none_of - like the standard versions, but over a tuple

  • apply - like std::apply, but also a member function on tuple

  • cartesian_product - create a tuple-of-tuples-of-references that is the cartesian product of the inputs

  • cartesian_product_copy - create a tuple-of-tuples that is the cartesian product of the inputs

  • chunk_by - split a tuple into a tuple-of-tuples according to a type function

  • contains_type - a variable template that is true when a tuple contains a given type

  • enumerate - like for_each, but including a compile-time index

  • filter - for compile-time filtering

  • for_each - like the standard version, but over a tuple

  • fold_left and fold_right - member functions on tuple

  • join - a member function on tuple, like fold_left but without an initial value

  • sort - sort a tuple by a function on the contained types

  • to_ordered_set - produce a tuple of unique types that are sorted

  • to_unordered_set - produce a tuple of unique types that are in the order given

  • transform - a variadic transform on tuple(s)

  • tuple_cat - like std::tuple_cat

  • unique - produce a tuple where adjacent types that are the same are merged into one element (the first such)

all_of, any_of, none_of

all_of, any_of and none_of work in the same way as the standard versions on ranges, but over a tuple instead.

auto t = stdx::tuple{1, 2, 3};
auto x = stdx::any_of([](auto n) { return n % 2 == 0; }, t); // true

apply

See member functions. stdx::apply is also available as a free function, for compatibility with std::apply.

auto t = stdx::tuple{1, 2, 3};
auto sum = stdx::apply([] (auto... args) { return (args + ... + 0); }, t); // 6

cartesian_product

cartesian_product takes any number of tuples and returns the tuple-of-tuples that is the cartesian product of the members. Each returned tuple is a tuple of references.

auto t1 = stdx::tuple{1, 2};
auto t2 = stdx::tuple{'a', 'b'};
auto c = stdx::cartesian_product(t1, t2);
// produces {{1, 'a'}, {1, 'b'}, {2, 'a'}, {2, 'b'}}
Note
The cartesian product of no tuples is a tuple containing an empty tuple.

cartesian_product_copy

The same as cartesian_product, but the returned tuples have values, not references.

Note
This can be useful for constexpr applications: in general one cannot take the address of a local constexpr variable unless it is static.

chunk_by

chunk_by takes a tuple and returns a tuple-of-tuples, where each tuple is grouped by type name.

auto t = stdx::tuple{1, 2, 3, true, false}; // tuple<int, int, int, bool, bool>
auto c1 = stdx::chunk_by(t); // tuple<tuple<int, int, int>, tuple<bool, bool>>
auto c2 = stdx::chunk(t);    // without a template argument, the same as chunk_by

Notice that chunk_by doesn’t sort the tuple first; it only chunks elements that are adjacent.

auto t = stdx::tuple{1, true, 3}; // tuple<int, bool, int>
auto c = stdx::chunk_by(t);      // tuple<tuple<int>, tuple<bool>, tuple<int>>

chunk_by takes an optional template argument which is a type function (a template of one argument). This will be applied to each type in the tuple to obtain a type name that is then used to chunk. By default, this type function is std::type_identity_t.

contains_type

contains_type is a variable template that is true when a tuple contains a given type.

using T = stdx::tuple<int, bool, int &>;
static_assert(stdx::contains_type<T, int>);

It also works on indexed tuples.

// see "Indexed tuples"
using T = stdx::indexed_tuple<stdx::detail::index_function_list<key_for>,
                              map_entry<X, int>, map_entry<Y, int>>;
static_assert(stdx::contains_type<T, X>);

If contains_type<Tuple, Type> is true, then you can use get<Type> to retrieve the appropriate member (assuming the type is contained exactly once).

enumerate

enumerate runs a given function object on each element of a tuple in order. Like for_each, it is variadic, taking an n-ary function and n tuples. The operator() of the given function object takes the std::size_t index (zero-based, of course) as an NTTP.

auto t = stdx::tuple{1, 2, 3};
stdx::enumerate([] <auto Idx> (auto x) { std::cout << Idx << ':' << x << '\n'; }, t);
Note
Like for_each, enumerate returns the function object passed to it.

enumerate is also available for std::array, but to be explicit it is called unrolled_enumerate:

auto a = std::array{1, 2, 3};
stdx::unrolled_enumerate([] <auto Idx> (auto x) { std::cout << Idx << ':' << x << '\n'; }, a);

filter

filter allows compile-time filtering of a tuple based on the types contained.

auto t = stdx::tuple{
  std::integral_constant<int, 1>{}, std::integral_constant<int, 2>{},
  std::integral_constant<int, 3>{}, std::integral_constant<int, 4>{}};

template <typename T>
using is_even = std::bool_constant<T::value % 2 == 0>;

auto filtered = stdx::filter<is_even>(t);
// filtered is a stdx::tuple<std::integral_constant<int, 2>,
//                           std::integral_constant<int, 4>>
Note
filtering a tuple can only be done on the types, not on the values! The type of the filtered result must obviously be known at compile time. However, the values within the tuple are also preserved.

for_each

for_each runs a given function on each element of a tuple in order. Like transform, it is variadic, taking an n-ary function and n tuples.

auto t = stdx::tuple{1, 2, 3};
stdx::for_each([] (auto x) { std::cout << x << '\n'; }, t);
Note
Like std::for_each, stdx::for_each returns the function object passed to it. This can be useful for stateful function objects.

for_each is also available for std::array, but to be explicit it is called unrolled_for_each:

auto a = std::array{1, 2, 3};
stdx::unrolled_for_each([] (auto x) { std::cout << x << '\n'; }, a);

sort

sort is used to sort a tuple by type name.

auto t = stdx::tuple{42, true}; // tuple<int, bool>
auto s = stdx::sort(t);         // tuple<bool, int> {true, 42}

Like chunk_by, sort takes an optional template argument which is a type function (a template of one argument). This will be applied to each type in the tuple to obtain a type name that is then sorted alphabetically. By default, this type function is std::type_identity_t.

to_sorted_set

to_sorted_set is sort followed by unique: it sorts the types in a tuple, then collapses it so that there is only one element of each type.

auto t = stdx::tuple{1, true, 2, false};
auto u = stdx::to_sorted_set(t); // {true, 1}

to_unsorted_set

to_unsorted_set produces a tuple of unique types in the same order as the original tuple. In each case the value of that type is the first one in the original tuple.

auto t = stdx::tuple{1, true, 2, false};
auto u = stdx::to_unsorted_set(t); // {1, true}

transform

transform is used to transform the values (and potentially the types) in one tuple, producing another.

auto t = stdx::tuple{1, 2, 3};
auto u = stdx::transform([](auto x) { return x + 1; }, t); // {2, 3, 4}

transform is not limited to working on a single tuple: given an n-ary function and n tuples, it will do the correct thing and "zip" the tuples together:

auto t1 = stdx::tuple{1, 2, 3};
auto t2 = stdx::tuple{2, 3, 4};
auto u = stdx::transform(std::multiplies{}, t1, t2); // {2, 6, 12}

transform can also apply indexing functions while it transforms:

// see "Indexed tuples"
struct X;
auto t = stdx::transform<key_for>(
  [](auto value) { return map_entry<X, int>{value}; },
  stdx::tuple{42});
auto x = get<X>(t).value; // 42

tuple_cat

tuple_cat works just like std::tuple_cat.

unique

unique works like std::unique, but on types rather than values. i.e. unique will collapse adjacent elements whose type is the same. The first such element is preserved in the result.

auto t = stdx::tuple{1, 2, true};
auto u = stdx::unique(t); // {1, true}

tuple_destructure.hpp

tuple_destructure.hpp allows the use of structured bindings with stdx::tuple.

auto t = stdx::tuple{1, 2};
auto &[x, y] = t;
Note
tie is not implemented.

type_traits.hpp

type_traits.hpp contains a few things from the standard:

always_false_v

always_false_v is a variable template that can be instantiated with any number of type arguments and always evaluates to false at compile-time. This is useful for writing static_assert where it must depend on types (at least before C++23 and P2593).

template <typename T>
auto f(T) {
  if constexpr (std::integral<T>) {
  } else {
    // doesn't work before C++23
    // static_assert(false, "S must be instantiated with an integral type");

    // instead, this works
    static_assert(stdx::always_false_v<T>, "S must be instantiated with an integral type");
  }
};

is_function_object_v

is_function_object_v is a variable template that detects whether a type is a function object, like a lambda. It is true for generic lambdas, too.

auto f() {};
auto const lam = []{};
auto const gen_lam = []<typename>(){};

stdx::is_function_object_v<decltype(f)>;         // false
stdx::is_function_object_v<decltype(lam)>;       // true
stdx::is_function_object_v<decltype(gen_lam)>;   // true

is_specialization_of_v

is_specialization_of_v is a variable template that detects whether a type is a specialization of a given template.

using O = std::optional<int>;

stdx::is_specialization_of_v<O, std::optional>;   // true
stdx::is_specialization_of_v<int, std::optional>; // false

is_specialization_of_v is suitable for templates with type parameters only (not template-template parameters or NTTPs). For templates with value parameters, use is_value_specialization_of_v.

template <auto N> struct S;
using T = S<0>;

stdx::is_value_specialization_of_v<T, S>; // true
Note
is_type_specialization_of_v is a synonym for is_specialization_of_v.

is_specialization_of is a function that can be used either way.

using O = std::optional<int>;
template <auto N> struct S;
using T = S<0>;

stdx::is_specialization_of<O, std::optional>(); // true
stdx::is_specialization_of<T, S>();             // true
Note
Perhaps until C++ has universal template parameters, there is no easy way to write this function where the template takes a mixture of type, value, and template parameters. So this is useful for templates whose parameters are all types or all values.

is_structural_v

is_structural_v<T> is true when T is a structural type suitable for use as a non-type template parameter.

static_assert(stdx::is_structural_v<int>);
static_assert(not stdx::is_structural_v<std::string>);
Note
Detecting structurality of a type is not yet possible in the general case, so there are certain structural types for which this trait will be false. In practice those types should be rare, and there should be no false positives.

type_or_t

type_or_t is an alias template that selects a type based on whether or not it passes a predicate. If not, a default is returned.

using A = int *;
using T = stdx::type_or_t<std::is_pointer, A>;        // A

using B = int;
using X = stdx::type_or_t<std::is_pointer, B>;        // void (implicit default)
using Y = stdx::type_or_t<std::is_pointer, B, float>; // float (explicit default)

type_list and value_list

type_list is an empty struct templated over any number of types. value_list is an empty struct templated over any number of NTTPs.

template_for_each

A type_list or a value_list can be iterated with template_for_each. A function object whose call operator is a unary function template with no runtime arguments is passed to each of these functions.

using L1 = stdx::type_list<std::integral_constant<int, 1>,
                           std::integral_constant<int, 2>>;
int x{};
stdx::template_for_each<L1>([&] <typename T> () { x += T::value; });
// x is now 3

using L2 = stdx::value_list<1, 2>;
int y{};
stdx::template_for_each<L2>([&] <auto V> () { y += V; });
// y is now 3
Note
A primary use case of template_for_each is to be able to use a list of tag types without those types having to be complete.

is_same_unqualified_v

is_same_unqualified_v is a variable template that detects whether a two types are the same are removing top-level cv-qualifications and references, if any.

stdx::is_same_unqualified_v<int, int const&>; // true
stdx::is_same_unqualified_v<int, void>;       // false

udls.hpp

Note
Taking a cue from the standard, all UDLs in stdx are declared in inline namespaces - typically stdx::literals.

Useful user-defined literals

udls.hpp contains a few handy user-defined literals so that code using boolean or small index values can be more expressive at the call site than just using bare true, false, or 0 through 9. This also makes it safer to use templates with bool or integral parameters.

using namespace stdx::literals;

template <bool X>
struct my_type { ... };

using my_type_with_X = my_type<"X"_true>;
using my_type_without_X = my_type<"X"_false>;

using my_type_with_X_alt = my_type<"X"_b>;
using my_type_without_X_alt = my_type<not "X"_b>;

auto t = stdx::tuple{1, true, 3.14f};
auto value = get<"X"_1>(t); // true
Note
The _N literals each return a std::integral_constant<std::size_t, N>. This is implicitly convertible to a std::size_t.

There are also some UDLs that are useful when specifying sizes in bytes:

using namespace stdx::literals;

// decimal SI prefixes
constexpr auto a = 1_k;  // 1,000
constexpr auto b = 1_M;  // 1,000,000
constexpr auto c = 1_G;  // 1,000,000,000

// binary equivalents
constexpr auto d = 1_ki; // 1,024
constexpr auto e = 1_Mi; // 1,048,567
constexpr auto f = 1_Gi; // 1,073,741,824

Integral and enum values

_c is a variable template whose value is a std::integral_constant with inferred type.

constexpr auto i = stdx::_c<1>;        // std::integral_constant<int, 1>

enum struct E { value = 1 };
constexpr auto e = stdx::_c<E::value>; // std::integral_constant<E, E::value>

_c is also a user-defined literal that can be used to make a std::integral_constant<std::uint32_t, N>.

using namespace stdx::literals;

constexpr auto i = 1_c; // std::integral_constant<std::uint32_t, 1>

utility.hpp

as_unsigned and as_signed

as_unsigned and as_signed are useful functions for converting integral types to their opposite-signed type. Under the hood, it’s a static_cast to the appropriate std::make_unsigned_t or std::make_signed_t.

auto x = 1729;                 // int
auto y = stdx::as_unsigned(x); // unsigned int

CX_VALUE

CX_VALUE is a macro that wraps its argument in a constexpr callable, which can be used as a non-type template argument. The compile-time value can be retrieved by calling the callable. This is useful for passing non-structural types as template arguments.

// A std::string_view value cannot be a template argument,
// so wrap it in CX_VALUE
constexpr auto ts_value = CX_VALUE(std::string_view{});
auto o = stdx::optional<std:string_view,
                        stdx::tombstone_value<ts_value>>;
Note
This is supported for C++20 and later: it still requires the ability to pass lambda expressions as non-type template arguments. The type must still be a literal type to be used at compile time, but need not necessarily be a structural type. (The usual difference is that structural types cannot have private members.)

forward_like

forward_like is an implementation of std::forward_like. forward_like_t is also provided.

static_assert(stdx::same_as<stdx::forward_like_t<int &, float>, float &>);

FWD

FWD is a macro that perfectly forwards its argument. It’s useful for C++17 lambdas which can’t have a template head to name their types.

// C++20 and later possibility
auto l = [] <typename Arg> (auto&& arg) {
  return f(std::forward<Arg>(arg));
};

// equivalent
auto l = [] (auto&& arg) {
  return f(FWD(arg));
};

overload

overload is a struct designed to encapsulate an overload set. It inherits from all the lambda expressions used to construct it. As an example, it’s useful for visiting a variant.

auto f(std::variant<int, float, std::string> const& v) {
  return std::visit(
    stdx::overload{
      [] (std::string const& s) { return s; },
      [] (auto num) { return std::to_string(num); }
    }, v);
}

sized

sized offers an easy way to convert array extents between arrays of different integral types.

How many times have you written something like this?

// I have N bytes, and I need to compute the number of uint32_ts to store them
const auto dword_size = (N + sizeof(std::uint32_t) - 1) / sizeof(std::uint32_t);

Or perhaps more likely and less portably:

const auto dword_size = (N + 3) / 4;

sized allows this conversion to be safer, more expressive, and more portable.

// I have N bytes, and I need to compute the number of uint32_ts to store them
const auto dword_size = stdx::sized8{N}.in<std::uint32_t>();

// I have M std::uint16_ts, and I need to compute the number of uint64_ts to store them
const auto qword_size = stdx::sized16{M}.in<std::uint64_t>();

// generally, I have K things of type T, I need to compute the number of Us to store them
const auto sz = stdx::sized<T>{K}.template in<U>();
  • sized8 is an alias for sized<std::uint8_t>

  • sized16 is an alias for sized<std::uint16_t>

  • sized32 is an alias for sized<std::uint32_t>

  • sized64 is an alias for sized<std::uint64_t>

The sized conversion works either from larger type to smaller type, or the other way around. Or even where the source and destination types are the same.

type_map

type_map is a structure designed to allow compile-time lookups of types or values. The basic idea is having a "map" of key-value pairs. Each key-value is a type_pair, and all the type_pair​s form a map. Values in the map can be looked up using type_lookup_t.

// A, B, C, X, and Y are types - they don't have to be complete
using M = stdx::type_map<stdx::type_pair<A, X>, stdx::type_pair<B, Y>>;
using T = stdx::type_lookup_t<M, A>; // X
using U = stdx::type_lookup_t<M, B>; // Y
using Z = stdx::type_lookup_t<M, C>; // void, because C is not in the map

type_lookup_t takes an optional third argument to be returned as the default (void above). The most common use case is for type_map to holds types, but there are convenience aliases for dealing with compile-time values in each of the four possibilities:

  • type_lookup_t - for mapping from types to types

  • type_lookup_v - for mapping from types to values

  • value_lookup_t - for mapping from values to types

  • value_lookup_v - for mapping from values to values

And type_pair has corresponding aliases to make the appropriate type_map​s:

  • tt_pair - for type-type maps

  • tv_pair - for type-value maps

  • vt_pair - for value-type maps

  • vv_pair - for value-value maps

// a type-type map that uses type_lookup_t
using M1 = stdx::type_map<stdx::tt_pair<A, X>, stdx::tt_pair<B, Y>>;
using T1 = stdx::type_lookup_t<M1, A>; // X

// a type-value map that uses type_lookup_v
using M2 = stdx::type_map<stdx::tv_pair<A, 0>, stdx::tv_pair<B, 1>>;
constexpr auto v2 = stdx::type_lookup_v<M2, A>; // 0

// a value-type map that uses value_lookup_t
using M3 = stdx::type_map<stdx::vt_pair<0, X>, stdx::vt_pair<1, Y>>;
using T3 = stdx::value_lookup_t<M3, 0>; // X

// a value-value map that uses value_lookup_v
using M4 = stdx::type_map<stdx::vv_pair<0, 42>, stdx::vv_pair<1, 17>>;
constexpr auto v4 = stdx::value_lookup_v<M4, 0>; // 42

In the case of mapping to types, the *_lookup_t aliases have optional third type arguments which are defaults returned when lookup fails. In the case of mapping to values, the *_lookup_v aliases have optional third NTTP arguments in the same role.

unreachable

unreachable is an implementation of std::unreachable.

[[noreturn]] inline auto unreachable() -> void {
  // if this function is ever called, it's
  // undefined behaviour
}