为什么 std::extent 不能处理对像运算符 sizeof 这样的数组的引用?

问题描述

如果我想获取数组的大小(元素数),我可以使用 sizeof(a) / sizeof(a[0]) 但新标准可以使用类型特征来做到这一点:

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";
}

一切正常,但为什么 std::extent 不能像 sizeof() 运算符那样处理对数组的引用?我必须删除一个示例中的引用才能获得实际的数组类型。

解决方法

这并没有直接回答问题,而是一个潜在的xy problem。你不应该也不需要使用 std::extent 来做到这一点。在这种情况下它没有用。使用 std::size 代替:

std::size(a)

现在来回答:

为什么 std::extent 对数组的引用不起作用

它有效;只是不是您可能一直期望的方式。特别是,任何引用的范围都是 0,而不是被引用类型的范围。

,

老实说,原因在于执行和标准; 看看这个的 GCC 实现:

  /// 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 == 0 ? 0 : extent<_Tp,_Uint - 1>::value>
    { };

甚至 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],I> {};
 
template<class T,std::size_t I,unsigned N>
struct extent<T[I],N-1> {};

T[] 不同于对数组的引用;所以它退回到默认的,就是这个:

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

始终为 0。

也可以自定义它以支持引用,但是您可能希望它支持 constvolatileconst volatile 以及实现者(如果它是我)是这样的(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&&)

是的,这样做不是很好吗?因此,实际上将这一点放在用户而不是库实现者或标准上会更方便,而且通常不会造成混淆。