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:

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…