为什么 std::array::size 不是静态的?

2022-01-07 00:00:00 c++ c++11 stl

std::array 的大小在编译时已知,但 size 成员函数不是静态的.有什么原因吗?如果不实例化对象就无法计算大小,这有点不方便.(嗯,我知道 std::tuple_size 特化,但它不适用于从 std::array 派生的类.)

The size of std::array is known at compile time, but the size member function isn't static. Is there any reason for that? It's slightly inconvenient not to be able to calculate the size without instantiating an object. (Well, I know about std::tuple_size specialization, but it doesn't work for classes derived from std::array.)

推荐答案

没有充分的理由. 事实上,boost::arraystd的前身::array<T,N>,实际上定义了static size_t size(){return N;}(虽然现代更有用的版本也应该使用constexpr).

There is no good reason for that. In fact, boost::array<T, N>, the precursor of std::array<T,N>, actually defines static size_t size(){return N;} (although a modern more useful version should use constexpr also).

我同意 OP 的观点,这是对语言功能的不幸遗漏和开发不足.

I agree with the OP that this is an unfortunate omission and underexplotaition of the language features.

问题

我之前遇到过这个问题,逻辑导致了几个解决方案.OP 情况如下:您有一个派生自 std::array 的类,您需要在编译时访问大小.

I faced this problem before and the logic leads to a couple of solutions. The OP situation is the following: you have a class that derives from std::array and you need to access to the size at compile time.

#include<array>

template<class T...>
struct myarray : std::array< something that depends on T... >{
    ... very cool functions...
};

后来你有

template<class Array, size_t N = ???>
functionOnArrayConcept(Array const& a){...}

编译时需要知道N的地方.

Where you need to know N at compile time.

就目前而言,没有代码 ??? 可以同时适用于 std::arraymyarray,因为 std::tuple_size> 将不起作用.

As it is now, there is no code ??? that you can write that works both for std::array and myarray, because std::tuple_size<myarray<...>> will not work.

解决方案

(这是@TC 在这里建议的在编译时访问最大模板深度? . 我只是复制到这里.)

(this was suggested by @T.C. here Access maximum template depth at compile? . I am just copying it here.)

template<class T, std::size_t N>
auto array_size_impl(const std::array<T, N>&) 
    -> std::integral_constant<std::size_t, N>;

template<class Array>
using array_size = decltype(array_size_impl(std::declval<const Array&>()));

template<class Array>
constexpr auto static_size() -> decltype(array_size<Array>::value){
    return array_size<Array>::value;
}
template<class Array>
constexpr auto static_size(Array const&) -> decltype(static_size<Array>()){
    return static_size<Array>();
}

现在你可以这样使用它:

Now you can use it as this:

template<class Array, size_t N = static_size<Array>()>
functionOnArrayConcept(Array const& a){...}

如果您已经在使用 std::tuple_size,不幸的是(我认为)您需要为每个派生类专门化 std::tuple_size:

If you are using std::tuple_size already, unfortunately (I think) you need to specialize std::tuple_size for each of your derived classes:

namespace std{
    template<class... T> // can be more complicated if myarray is not parametrized by classes only
    struct tuple_size<myclass<T...>> : integral_constant<size_t, static_size<myclas<T...>>()>{};
}

(在我看来,这是由 STL 设计中的另一个错误引起的,即 std::tuple_size 没有默认的 template struct tuple_size : A::size(){}.)

(In my opinion this is caused by another mistake in the STL design that std::tuple_size<A> doesn't have the default template<class A> struct tuple_size : A::size(){}.)

与@T.C.相比,超出这一点的解决方案几乎过时了.上面描述的解决方案.我将它们保留在这里仅供参考.

The solutions beyond this point are near obsolete compared to @T.C. solution described above. I'll keep them here for reference only.

解决方案 1(惯用语)

如果函数与你的类分离,你必须使用 std::tuple_size 因为这是访问 std::array 大小的唯一标准方式编译时间.因此你必须这样做,1) 提供 std::tuple_size 的特化,如果你可以控制 myclass,2) std::array 没有 static size() 但您的派生类可以(这简化了解决方案).

If the function is decoupled from you class you have to use std::tuple_size because that is the only standard way of accessing the size of std::array at compile time. Therefore you have to do this, 1) provide a specialization of std::tuple_size and if you can control myclass, 2) std::array doesn't have static size() but your derived class could (that simplifies the solution).

因此,这可以是 STD 框架内的一个非常通用的解决方案,包括 std::tuple_size 的专业化.(不幸的是,在 std:: 中提供专业化有时是制作真正通用代码的唯一方法.请参阅 http://en.cppreference.com/w/cpp/language/extending_std)

So, this can be a pretty general solution within the framework of STD, that consists in the specialization of std::tuple_size. (Unfortunately providing specialization in std:: sometimes is the only way to make real generic code. See http://en.cppreference.com/w/cpp/language/extending_std)

template<class... T>
struct myarray : std::array<...something that depends on T...>{
    ... very cool functions...
    static constexpr size_t size(){return std::tuple_size<std::array<...something that depends on T...>>::value;}
};

namespace std{
    // specialization of std::tuple_size for something else that `std::array<...>`.
    template<class... T> // can be more complicated if myarray is not parametrized by classes only
    struct tuple_size<myclass<T...>> : integral_constant<size_t, myclass<T...>::size()>{};
}

// now `functionOnArrayConcept` works also for `myarray`.

(static size_t size() 可以有不同的调用方式,可能还有其他方法可以推导出myarray 的基数的大小,而无需向<添加任何静态函数代码>大小.)

(static size_t size() can be called differently, and there may be other ways to deduce the size of the base of myarray without adding any static function to size.)

注意

在编译器中,我尝试了以下技巧不起作用.如果这行得通,整个讨论就不那么重要了,因为 std::tuple_size 就没有那么必要了.

In the compilers I tried the following trick doesn't work. If this worked, the whole discussion would be less important, because std::tuple_size wouldn't be so necessary.

template<class ArrayConcept, size_t N = ArrayConcept{}.size()> // error "illegal expression", `std::declval<ArrayConcept>()` doesn't work either.
functionOnArrayConcept(ArrayConcept const& a){...}

概念化

由于 std::array 的实现(或规范?)中的这个缺点,提取编译时间 size 的唯一方法是通过 std::tuple_size.那么std::tuple_size概念上是std::array必要接口的一部分.因此,当您从 std::array 继承时,您在某种意义上也继承"了 std::tuple_size.不幸的是,您需要这样做以进一步推导.这是这个答案背后的概念.

Due to this shortcoming in the implementation (or specification?) of std::array by which the only way to extract the compile time size is through std::tuple_size. Then std::tuple_size is conceptually part of the necessary interface of std::array. Therefore when you inherit from std::array you have also "inherit" std::tuple_size in some sense. And unfortunately you need to do this for further derivations. This is the concept behind this answer.

解决方案 2(GNU hack)

如果您使用的是 GNU 的 STD 库(包括 gccclang),则有一种无需添加任何代码即可使用的 hack,即通过使用_M_elems 成员,它是 std 的(成员)类型 ::_AT_Type::_Type(又名类型 T[N])::array.

If you are using GNU's STD library (that includes gcc and clang), there is a hack that can be used without adding any code, and that is by using the _M_elems member which is of (member) type ::_AT_Type::_Type (a.k.a. type T[N]) of std::array<T, N>.

此函数将有效地表现得像 std::array 的静态函数 ::size()(除了它不能用于对象的实例)或从 std::array 派生的任何类型.

This function will effectively behave like a static function ::size() (except that it cannot be used for instances of an object) of std::array or any type derived from std::array.

std::extent<typename ArrayType::_AT_Type::_Type>::value

可以包装成:

template<class ArrayType>
constexpr size_t array_size(){
    return std::extent<typename ArrayType::_AT_Type::_Type>::value
}

这项工作是因为成员类型 _AT_Type::_Type 是继承的.(我想知道为什么 GNU 留下这个实现细节public.另一个遗漏?)

This work because the member type _AT_Type::_Type is inherited. (I wonder why GNU left this implementation detail public. Another omission?)

解决方案 3(便携式黑客)

最后,使用模板递归的解决方案可以算出基础std::array的维数.

Finally, a solution using template recursion one can figure out what is the dimension of the base std::array.

template<class Array, size_t N=0, bool B = std::is_base_of<std::array<typename Array::value_type, N>, Array>::value>
struct size_of : size_of<Array, N + 1, std::is_base_of<std::array<typename Array::value_type, N+1>, Array>::value>{};

template<class Array, size_t N>
struct size_of<Array, N, true> : std::integral_constant<size_t, N>{};

// this is a replacement for `static Array::size()`    
template<class Array, size_t N = size_of<Array>::value>
constexpr size_t static_size(){return N;}

// this version can be called with an object like `static Array::size()` could
template<class Array, size_t N = size_of<Array>::value>  
constexpr size_t static_size(Array const&){return N;}

这是如何获得的:

struct derived : std::array<double, 3>{};

static_assert( static_size<std::array<double, 3>>() == 3 );
static_assert( static_size<derived>() == 3 );
constexpr derived d;
static_assert( static_size(d) == 3 );

如果使用与std::array 无关的某种类型调用此函数,则会出现递归错误.如果你想要一个软"错误,你必须添加特化.

If this function is called with some type unrelated to std::array, it will give a recursion error. If you want a "soft" error instead, you have to add the specialization.

template<class Array>
struct size_of<Array, 250, false> {}; 

其中 250 代表大数但小于递归限制.(我不知道如何自动获取这个数字,我只知道我的编译器中的递归限制是256.)

where 250 stands for a large number but smaller than the recursion limit. (I don't know how to get this number automatically, I only know the the recursion limit in my compiler is 256.)

相关文章