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

liz*_*isk 30 c++ stl c++11

大小std::array在编译时已知,但size成员函数不是静态的.有什么理由吗?在没有实例化对象的情况下不能计算大小有点不方便.(嗯,我知道std::tuple_size专业化,但它不适用于派生自的类std::array.)

Luk*_*son 8

从C++ 11开始,您可以在std :: array上使用std :: tuple_size来获取编译时常量的大小.看到

http://en.cppreference.com/w/cpp/container/array/tuple_size


alf*_*lfC 8

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

我同意OP的观点,这是一个令人遗憾的遗漏和语言特征的低估.

问题

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

#include<array>

template<class T...>
struct myarray : std::array< something that depends on T... >{
    ... very cool functions...
};
Run Code Online (Sandbox Code Playgroud)

后来你有

template<class Array, size_t N = ???>
functionOnArrayConcept(Array const& a){...}
Run Code Online (Sandbox Code Playgroud)

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

因为它是现在,也没有代码???,你可以写的作品无论是std::arraymyarray,因为std::tuple_size<myarray<...>>将无法正常工作.

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

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>();
}
Run Code Online (Sandbox Code Playgroud)

现在你可以这样使用它:

template<class Array, size_t N = static_size<Array>()>
functionOnArrayConcept(Array const& a){...}
Run Code Online (Sandbox Code Playgroud)

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

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...>>()>{};
}
Run Code Online (Sandbox Code Playgroud)

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


与上述@TC解决方案相比,超出这一点的解决方案已接近过时.我会把它们留在这里仅供参考.

解决方案1(惯用)

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

因此,这可以是STD框架内的一个非常通用的解决方案,其中包括专业化std::tuple_size.(不幸的是,提供专业化std::有时是制作真正通用代码的唯一方法.请参阅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`.
Run Code Online (Sandbox Code Playgroud)

(static size_t size()可以不同地调用,并且可能有其他方法来推断基础的大小myarray而不添加任何静态函数size.)

注意

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

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

概念化

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

解决方案2(GNU hack)

如果您正在使用GNU的STD库(包括gccclang),则可以使用hack而无需添加任何代码,这是通过使用_M_elems(成员)类型::_AT_Type::_Type (也称为类型T[N])的成员std::array<T, N>.

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

std::extent<typename ArrayType::_AT_Type::_Type>::value
Run Code Online (Sandbox Code Playgroud)

可以包装成:

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

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

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

最后,使用模板递归的解决方案可以弄清楚基础的维度是什么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;}
Run Code Online (Sandbox Code Playgroud)

这就是人们将得到的:

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 );
Run Code Online (Sandbox Code Playgroud)

如果使用某种与之无关的函数调用此函数std::array,则会产生递归错误.如果您想要"软"错误,则必须添加特化.

template<class Array>
struct size_of<Array, 250, false> {}; 
Run Code Online (Sandbox Code Playgroud)

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


小智 1

它确实可以是静态的,但是,这会破坏“容器”接口,该接口不能与其他期望容器具有size()成员函数的通用算法很好地配合。不过,没有什么可担心的,就像函数std::array::size()一样constexpr,因此绝对没有与之相关的开销。

更新:

Jrok 先生指出,可以用“正常”语法调用静态成员函数。下面是一个不会出现这种情况的示例:

#include <array>

struct array {
    static unsigned int size()
    {
        return 0;
    }
};

template <typename T>
static auto do_stuff(T& data) -> decltype(data.size())
{
    typedef decltype(data.size()) size_type;
    size_type (T::*size_calc)() const = &T::size;
    size_type n = 0;
    for (size_type i = 0, e = (data.*size_calc)(); i < e; ++i)
        ++n;
    return n;
}

int main()
{
    // Below is fine:
    std::array<int, 5> data { 1, 2, 3, 4, 5 };
    do_stuff(data);

    // This, however, won't work as "size()" is not a member function.
#if 0
    array fake;
    do_stuff(fake);
#endif
}
Run Code Online (Sandbox Code Playgroud)

  • 很好的一点是,某些通用代码可能会因静态成员函数而失败,但是实际的容器要求并不要求容器允许指向“size()”的成员指针;所需要的只是表达式“a.size()”起作用,并且静态成员函数满足该要求。 (15认同)
  • 您可以使用“正常”语法调用静态成员函数,那么这会如何破坏接口呢? (7认同)