Repository navigation
Opt-in constexpr inline slots for constexpr-but-not-trivially default constructible types #1
Description
motivation
inline_storage is constant-evaluable only when detail::constexpr_inline_slots<T> holds, which requires T to be trivially default constructible. A type that is constexpr-default-constructible but not trivially so (anything with a default member initialiser, or a user-provided constexpr constructor) falls back to the byte-array specialisation and is shut out of constant evaluation entirely, even though nothing about it would actually prevent it.
The solution could be to keep the current default, and add an opt-in storage that uses T slots[N] for such types, accepting the default initialisation those types already imply.
The current rule is right, but it is too strict.
The trait is exactly right for the default. T slots[N] for a trivially default constructible T constructs nothing, so the storage keeps its defining property: N uninitialised slots, no work at construction.
The strictness only bites for a type whose default constructor is non-trivial but is constexpr. For such a type there is no "uninitialised" option to protect in the first place: its own semantics say that default-initialising it runs code. Today those types get the byte array, which does skip the initialisation but also gives up constexpr. There is no third choice.
Worked example. sgl::vec2 used to be:
struct alignas(8) vec2 { float x{}, y{}; }; // not trivially default constructiblelibmem::inline_vector<vec2, 8> was therefore non-constexpr, so a downstream class holding one could not be built at compile time. That specific case was fixed on the sgl side by dropping the initialisers, but that fix is only available to someone who owns the element type. A consumer holding a third-party type with an NSDMI has no such option.
proposal
An additional slot policy, selected explicitly rather than by trait, so nothing changes for existing users:
// today: uninitialised slots, constexpr only for trivially default constructible T
libmem::inline_vector<T, N>
// proposed: T slots[N] are default-initialised, constexpr for any constexpr-constructible T
libmem::constexpr_inline_vector<T, N>Implementation is small: a third inline_slots specialisation holding T slots[N]{} (or default-initialised, whichever the semantics call for), plus an alias and a static_capacity/relocatable surface identical to the current one. basic_vector needs no change: it already routes construction through std::construct_at, which is constexpr.
The same policy would flow to sparse_set / sparse_map over inline storage, which take their storage as a template parameter already.
prototype
Confirmed reachable before filing. A minimal storage using std::array<T, N> plus a vector that constructs through std::construct_at constant-evaluates for both a trivially default constructible aggregate and a type with a user-provided constexpr constructor:
struct with_ctor {
int v;
constexpr with_ctor(int a, int b) : v{a + b} {}
constexpr with_ctor() : v{0} {}
};
constexpr int sum() {
proto_inline_vector<with_ctor, 4> v{};
v.emplace_back(2, 3);
v.emplace_back(10, 20);
return v[0].v + v[1].v;
}
static_assert(sum() == 35); // passeswith_ctor is not trivially default constructible, so it is exactly the case the current
trait excludes.
opt-in
It's not free and thus must be opt-in
T slots[N] runs N default constructors at storage construction. For a type whose default constructor zeroes, that is the memset the current design deliberately avoids paying for unused slots. That cost is acceptable when the caller asks for it by name and unacceptable as a silent default, hence a separate alias rather than a relaxed trait.
Activity