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

时间:2014-02-21 13:59:21

标签: c++ c++11 stl

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

6 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:5)

没有充分的理由。事实上,std::array<T,N>的前身boost::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...
};

以后你有

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

您需要在编译时了解N

就像现在一样,您可以编写的代码???同时适用于std::arraymyarray,因为std::tuple_size<myarray<...>>无效。

解决方案

(这是@ T.C。Access maximum template depth at compile?建议的。我只是在这里复制它。)

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>();
}

现在你可以这样使用它:

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

如果您已经使用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...>>()>{};
}

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

  

与@ T.C相比,超越这一点的解决方案已经过时了。   上述解决方案。我会把它们留在这里仅供参考。

解决方案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`.

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){...}

<强>概念化

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

解决方案2(GNU黑客)

如果您正在使用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

可以包装成:

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

这项工作是因为成员类型_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;}

这是一个人将得到的:

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无关的某种类型调用此函数,则会产生递归错误。如果您想要“软”错误,则必须添加专业化。

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

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

答案 2 :(得分: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
}

答案 3 :(得分:0)

array::sizeconstexpr,因此除非存储的类型具有构造函数或析构函数,否则操作array_t().size()不太可能具有任何运行时效果。您可以将其嵌入模板参数中以确保它不会。但它确实看起来像运行时代码

我认为它只是为了与其他容器的一致性而非静止。例如,您可以为其创建指向成员的函数。然而,发现任何事物的真正理由通常都需要进行艰苦的研究。可能是作者从未想过它。

我想到的另一件事是,operator () ()之类的一些特殊函数不能是静态的,所以静态的任何机会性应用都只能是零碎的。通用问题可以更好地以统一的方式解决,即使这意味着改变核心语言。

答案 4 :(得分:-1)

请注意,Microsoft Visual C ++目前不支持constexpr(http://msdn.microsoft.com/en-us/library/hh567368.aspx),因此以下有效代码不起作用:

    array<int,3> dog;
    array<double, dog.size( )> cat;

以下类提供了编译时静态变量:

/**
* hack around MSVC's 2012 lack of size for const expr
*/
template <typename T, int N>
struct vcarray : public std::array<T,N> {
    static const size_t ArraySize= N;
};

可以用作:

vcarray<double,3> cat;
vcarray<double,cat.ArraySize> dog;

答案 5 :(得分:-3)

在我看来,只要size成员函数static没有提供附加价值就没有意义。它可以成为static,但你从中获得的一切都没有。

array类的设计方式,您可以查询给定array对象的大小,而无需在需要的位置明确知道/记住其确切类型(包括其大小)尺寸。这是一种方便,它消除了制作复制/编辑错误的机会。您可以编写如下代码:

std::array<int, 5> blah;
// 50 lines of code
do_something_with(blah.size()); // blah knows its size

正如你所看到的,在我消耗数组大小的位置,我实际上并不记得它是什么,但我的代码无论如何都会工作,无论实际值是什么,无论是否有一个我将数组的类型更改为不同的大小 由于size函数只返回一个模板参数,编译器可以简单地证明返回值是编译时常量并相应地进行优化(函数也是constexpr,所以你也可以使用返回值作为模板参数或枚举)。

现在,如果我们制作size成员函数static会有什么不同?

如果sizestatic函数,您可以仍然以完全相同的方式使用静态成员函数(即,在对象实例上,在“不是静态的方式“),但那将是”作弊“。毕竟,无论成员是static还是array,这都已经有效了 此外,您现在可以在没有对象实例的情况下调用成员函数。虽然乍一看这似乎是一件好事static类模板(...其中返回的大小是模板参数)确实没有任何优势

为了在没有对象的情况下调用成员函数(即,以“std::array<int, 5> blah; // 50 lines of code do_something_with(std::array<int,5>::size()); // I must tell size what to return 成员函数方式”),您必须使用类名及其正确的模板参数正确限定函数。
换句话说,你必须写一些类似的东西:

size

现在我们通过调用{{1}}函数获得了什么?什么都没有。为了调用该函数,我们需要提供正确的模板参数,其中包含的大小。

这意味着我们必须提供我们希望查询的信息。调用函数并没有告诉我们任何我们不知道的事情。