Why doesn't std::extent work on references to arrays like operator sizeof?
Asked Answered
H

2

6

If I want to get the size (number of elements) of an array I can use sizeof(a) / sizeof(a[0]) but the new standard makes it possible to use type traits to do that:

int main(){

    int a[]{5, 7, 2, 3, 6, 7};

    std::cout << std::extent<decltype(a)>::value << '\n'; // 6

    auto& refA = a;
    std::cout << std::extent<decltype(refA)>::value << '\n'; // 0
    std::cout << sizeof(refA) / sizeof(refA[0]) << '\n'; // 6
    std::cout << std::extent<std::remove_reference<decltype(refA)>::type>::value << '\n'; // 6
 
    std::cout << "\ndone!\n";
}

Everything is OK but why doesn't std::extent work on references to arrays as the sizeof() operator does? I've had to remove the reference in the last example to get the actual array type.

Hangout answered 16/1, 2021 at 21:51 Comment(2)
Most traits requires exact type. You might also use std::decay o adapt as your need.Ami
Great explanation! thanks!Hangout
O
2

This doesn't answer the question as written directly, but rather a potential xy problem. You shouldn't nor do you need to use std::extent to do this. It's not useful in this case. Use std::size instead:

std::size(a)

Now for the answer:

why doesn't std::extent work on references to arrays

It works; just not in the way you may have been expecting. In particular, the extent of any reference is 0 rather than the extent of the referred type.

Oakum answered 17/1, 2021 at 7:22 Comment(0)
G
0

The reason lies behind the implementation and the standard to be honest; Look at the GCC implementation of this:

  /// extent
  template<typename, unsigned _Uint>
    struct extent
    : public integral_constant<std::size_t, 0> { };

  template<typename _Tp, unsigned _Uint, std::size_t _Size>
    struct extent<_Tp[_Size], _Uint>
    : public integral_constant<std::size_t,
                   _Uint == 0 ? _Size : extent<_Tp,
                               _Uint - 1>::value>
    { };

  template<typename _Tp, unsigned _Uint>
    struct extent<_Tp[], _Uint>
    : public integral_constant<std::size_t,
                   _Uint == 0 ? 0 : extent<_Tp,
                               _Uint - 1>::value>
    { };

And even the possible implementation specified in the cppreference:

template<class T, unsigned N = 0>
struct extent : std::integral_constant<std::size_t, 0> {};
 
template<class T>
struct extent<T[], 0> : std::integral_constant<std::size_t, 0> {};
 
template<class T, unsigned N>
struct extent<T[], N> : std::extent<T, N-1> {};
 
template<class T, std::size_t I>
struct extent<T[I], 0> : std::integral_constant<std::size_t, I> {};
 
template<class T, std::size_t I, unsigned N>
struct extent<T[I], N> : std::extent<T, N-1> {};

T[] is different from a reference to an array; so it falls back on the default one which is this one:

    template<class T, unsigned N = 0>
    struct extent : std::integral_constant<std::size_t, 0> {};

which is 0 all the time.

It is possible to customize it to support references as well, but then you probably would want it to support const and volatile and const volatile and other stuff which the implementer (if it was me) made it like this (from my web++ project):

        WEBPP_REMOVE_CVREF()
        WEBPP_REMOVE_CVREF(const)
        WEBPP_REMOVE_CVREF(volatile)
        WEBPP_REMOVE_CVREF(const volatile)
        WEBPP_REMOVE_CVREF(&)
        WEBPP_REMOVE_CVREF(&&)
        WEBPP_REMOVE_CVREF(const&)
        WEBPP_REMOVE_CVREF(const&&)
        WEBPP_REMOVE_CVREF(volatile&)
        WEBPP_REMOVE_CVREF(volatile&&)
        WEBPP_REMOVE_CVREF(const volatile&)
        WEBPP_REMOVE_CVREF(const volatile&&)

Yeah it's not pretty to do this right? So it's more convenient and usually less confusing actually to do put this on the user and not the library implementer or the standard.

Goldarn answered 16/1, 2021 at 22:36 Comment(1)
Why did you post images of text? Please do not post screenshots - please post text as text instead.Desirae

© 2022 - 2024 — McMap. All rights reserved.