C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Risks of using std::extent on non-built-in array types

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Fri, 13 Dec 2024 15:32:27 +0000
On Fri, 13 Dec 2024 at 15:09, Yexuan Xiao via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> Today, I noticed this code:
>
> for(std::size_t i{}; i != std::extent_v<arr>; ++i) { /* */ };
>
> When arr is a built-in array type, this works fine. However, when arr is a
> std::array<T, N> or another class type that mimics std::array, the loop
> body will never execute.
>
> The C++ standard states <https://eel.is/c++draft/meta.unary.prop.query#1>:
>
> If T is not an array type, or if it has a rank less than or equal to I, or
> if I is 0 and T has type 'array of unknown bound of U', then the result is
> 0.
>
>
> For array-like types, size is usually equivalent to extent, such as
> std::dynamic_extent indicating that std::span has a dynamic size. However,
> using std::extent only produces the correct value for built-in arrays,
> which can confuse users or cause errors.
>
> Some programming guidelines recommend using std::array instead of built-in
> arrays. During refactoring, this error might occur and be particularly
> insidious because users might not know that std::extent_v<std::array<T, N>>
> always results in 0. They might forget about it or store this result in an
> unintuitive way.
>
> Therefore, I propose that std::extent should use static_assert to reject
> any non-built-in array types.
>
> I believe this change is safe: no correct code would become erroneous and
> still compile.
>

This is false. Checking for non-zero std::extent_v<T> can be used as an
alternative to is_bounded_array, e.g.

  template<typename _Tp>
    requires is_array_v<_Tp> && (extent_v<_Tp> == 0)
    using _UnboundedArray = _Tp;

  template<typename _Tp>
    requires (extent_v<_Tp> != 0)
    using _BoundedArray = _Tp;

This is real code in libstdc++. I would be surprised if nobody else has
ever done that.



> While I'm not a metaprogramming expert and am unsure of the exact impact
> on metaprogramming, some code may depend on this behavior. However,
> reverting to the previous behavior is inexpensive:
>
> std::conditional<std::is_array_v<T>, std::extent<T>,
> std::integral_constant<std::size_t, 0>>
>
> although it’s somewhat verbose.
>

std::conditional can be surprisingly expensive for compilation speed.

conjunction<is_array<int[3]>, extent<int[3]>>::value also works (but
replacing that with conjunction_v doesn't!)

Received on 2024-12-13 15:33:46