The new static constexpr std::integral_constant idiom
This blog post was first published at think-cell's developer blog. Subscribe there to stay up-to-date!
The size of std::array<T, N>
is known at compile-time given the type.
Yet it only provides a regular .size()
member function:
template <typename T, std::size_t N>
struct array {
constexpr std::size_t size() const {
return N;
}
};
This is annoying if you’re writing generic code that expects some sort of compile-time sized range.
template <typename Rng>
void algorithm(Rng const& rng) {
constexpr auto a = Rng::size(); // error, std::array has no static size
constexpr auto b = rng.size(); // error, not a constant expression
constexpr auto c = std::tuple_size<Rng>::value; // okay, but ugly
}
It would be really nice if std::array::size
were a static
member function instead:
template <typename T, std::size_t N>
struct array {
static constexpr std::size_t size() {
return N;
}
};
Now we can just use ::size()
to get the size of an array without breaking existing code:
You can still call static member functions with .
syntax;
foo.static_member(args)
is equivalent to std::remove_cvref_t<decltype(foo)>::static_member(args)
.
There are MISRA guidelines against it and a clang-tidy check that complains, but it is really useful for generic code.
However, sometimes even Rng::size()
isn’t enough.
Suppose we want to have a std::integral_constant
as the result, so we can do type-based metaprogramming.
Sure, we can then write std::integral_constant<std::size_t, Rng::size()>{}
, but that’s a lot to type.
It would be really convenient if std::array
had that directly, as that is the “most constexpr
” version of a size:
template <typename T, std::size_t N>
struct array {
static constexpr std::integral_constant<std::size_t, N> size = {};
};
Now we can write array::size
and get a std::integral_constant
that represents the size.
But we’ve broken all code that assumed .size()
or ::size()
?
You’d think so, but no: std::integral_constant
has a call operator!
template <typename T, T Value>
struct integral_constant {
constexpr T operator()() const {
return Value;
}
};
So with a single member, we support:
array::size
, which results in astd::integral_constant
object.array::size()
, which calls theoperator()
on thestd::integral_constant
and returns astd::size_t
.array_obj.size()
, which is the same as above and returns astd::size_t
, just as a member function.
Now that is nice!
The library evolution group of the C++ committee is considering that idiom for all new standard library components with constant sizes, like std::simd
, or std::inplace_vector::capacity
(formerly std::static_vector::capacity
).
Of course, we can’t actually change std::array
due to ABI or something…