The new static constexpr std::integral_constant idiom

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;
    }
};
std::array::size

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
}
Generic code that needs a constant size

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;
    }
};
Better std::array::size

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 const" version of a size:

template <typename T, std::size_t N>
struct array {
    static constexpr std::integral_constant<std::size_t, N> size = {};
};
Best std::array::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;
    }
};
std::integral_constant::operator()

So with a single member, we support:

  • array::size, which results in a std::integral_constant object.
  • array::size(), which calls the operator() on the std::integral_constant and returns a std::size_t.
  • array_obj.size(), which is the same as above and returns a std::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...

— by Jonathan Müller

Do you have feedback? Send us a message at devblog@think-cell.com !

Sign up for blog updates

Don't miss out on new posts! Sign up to receive a notification whenever we publish a new article.

Just submit your email address below. Be assured that we will not forward your email address to any third party.

Please refer to our privacy policy on how we protect your personal data.

Share