16

这只是过去几天困扰我的事情,我认为不可能解决,但我以前见过模板魔术。

开始:

要获取标准 C++ 数组中的元素数量,我可以使用宏 (1) 或类型安全的内联函数 (2):

(1)

#define sizeof_array(ARRAY) (sizeof(ARRAY)/sizeof(ARRAY[0]))

(2)

template <typename T>
size_t sizeof_array(const T& ARRAY){
    return (sizeof(ARRAY)/sizeof(ARRAY[0]));
}

如您所见,第一个存在宏的问题(目前我认为这是一个问题),而另一个存在无法在编译时获取数组大小的问题;即我不能写:

enum ENUM{N=sizeof_array(ARRAY)};

或者

BOOST_STATIC_ASSERT(sizeof_array(ARRAY)==10);// Assuming the size 10..

有谁知道这是否可以解决?

更新

这个问题是在引入 constexpr 之前创建的。现在你可以简单地使用:

template <typename T>
constexpr auto sizeof_array(const T& iarray) {
    return (sizeof(iarray) / sizeof(iarray[0]));
}
4

10 回答 10

22

从这里尝试以下操作:

template <typename T, size_t N>
char ( &_ArraySizeHelper( T (&array)[N] ))[N];
#define mycountof( array ) (sizeof( _ArraySizeHelper( array ) ))

int testarray[10];
enum { testsize = mycountof(testarray) };

void test() {
    printf("The array count is: %d\n", testsize);
}

它应该打印出:“数组计数为:10”

于 2009-09-30T20:37:04.403 回答
19

在 C++1xconstexpr中,你会发现:

template <typename T, size_t N>
constexpr size_t countof(T(&)[N])
{
    return N;
}
于 2009-09-30T20:21:19.620 回答
8

我能想到的最好的是:

template <class T, std::size_t N>
char (&sizeof_array(T (&a)[N]))[N];

// As litb noted in comments, you need this overload to handle array rvalues
// correctly (e.g. when array is a member of a struct returned from function),
// since they won't bind to non-const reference in the overload above.
template <class T, std::size_t N>
char (&sizeof_array(const T (&a)[N]))[N];

必须与另一个一起使用sizeof

int main()
{
    int a[10];
    int n = sizeof(sizeof_array(a));
    std::cout << n << std::endl;
}

[编辑]

想一想,我相信这在 C++03 中的单个“类似函数的调用”中是不可能做到的,除了宏,这就是原因。

一方面,您显然需要模板参数推导来获得数组的大小(直接或通过sizeof您所做的)。但是,模板参数推导只适用于函数,不适用于类;即,您可以有一个类型为reference-to-array-of-N 的模板参数R,其中N 是另一个模板参数,但您必须在调用时同时提供R 和N;如果你想从 R 中推导出 N,只有函数调用可以做到这一点。

另一方面,任何涉及函数调用的表达式都可以是常量的唯一方法是当它位于内部时sizeof。其他任何事情(例如,在函数的返回值上访问静态或枚举成员)仍然需要发生函数调用,这显然意味着这不会是一个常量表达式。

于 2009-09-30T20:37:51.250 回答
6

这不完全是您正在寻找的东西,但它很接近 - 一个片段,winnt.h其中包含对 #$%^ 它正在做什么的一些解释:

//
// RtlpNumberOf is a function that takes a reference to an array of N Ts.
//
// typedef T array_of_T[N];
// typedef array_of_T &reference_to_array_of_T;
//
// RtlpNumberOf returns a pointer to an array of N chars.
// We could return a reference instead of a pointer but older compilers do not accept that.
//
// typedef char array_of_char[N];
// typedef array_of_char *pointer_to_array_of_char;
//
// sizeof(array_of_char) == N
// sizeof(*pointer_to_array_of_char) == N
//
// pointer_to_array_of_char RtlpNumberOf(reference_to_array_of_T);
//
// We never even call RtlpNumberOf, we just take the size of dereferencing its return type.
// We do not even implement RtlpNumberOf, we just decare it.
//
// Attempts to pass pointers instead of arrays to this macro result in compile time errors.
// That is the point.
//
extern "C++" // templates cannot be declared to have 'C' linkage
template <typename T, size_t N>
char (*RtlpNumberOf( UNALIGNED T (&)[N] ))[N];

#define RTL_NUMBER_OF_V2(A) (sizeof(*RtlpNumberOf(A)))

RTL_NUMBER_OF_V2()宏最终被用于更具可读性的ARRAYSIZE()宏中。

Matthew Wilson 的“Imperfect C++”一书也讨论了这里使用的技术。

于 2009-09-30T22:07:40.397 回答
6

问题

我喜欢Adisak 的回答

template <typename T, size_t N>
char ( &_ArraySizeHelper( T (&arr)[N] ))[N];
#define COUNTOF( arr ) (sizeof( _ArraySizeHelper( arr ) ))

这是微软在 VS2008 中用于_countof 宏的,它有一些不错的特性:

  • 它在编译时运行
  • 它是类型安全的(即,如果你给它一个指针,它会产生一个编译时错误,数组太容易降级了)

但正如Georg所指出的,这种方法使用模板,因此不能保证使用 C++03 的本地类型

void i_am_a_banana() {
  struct { int i; } arr[10];
  std::cout << COUNTOF(arr) << std::endl; // forbidden in C++03
}

幸运的是,我们没有走运。

解决方案

Ivan Johnson 提出了一个在所有方面都获胜的聪明方法:它是类型安全的、编译时的,并且适用于本地类型:

#define COUNTOF(arr) ( \
   0 * sizeof(reinterpret_cast<const ::Bad_arg_to_COUNTOF*>(arr)) + \
   0 * sizeof(::Bad_arg_to_COUNTOF::check_type((arr), &(arr))) + \
   sizeof(arr) / sizeof((arr)[0]) )

struct Bad_arg_to_COUNTOF {
   class Is_pointer; // incomplete
   class Is_array {};
   template <typename T>
   static Is_pointer check_type(const T*, const T* const*);
   static Is_array check_type(const void*, const void*);
};

对于那些感兴趣的人,它通过在标准的基于 sizeof 的数组大小宏之前插入两个“测试”来工作。这些测试不会影响最终计算,但旨在为非数组类型生成编译错误:

  1. 第一个测试失败,除非arr是整数、枚举、指针或数组。 reinterpret_cast<const T*>对于任何其他类型都应该失败。
  2. 对于整数、枚举或指针类型,第二个测试失败。

    整数和枚举类型将失败,因为它们没有check_type匹配的版本,因为check_type需要指针。

    指针类型将失败,因为它们将匹配 的模板化版本check_type,但模板化的返回类型 ( Is_pointer)check_type不完整,这将产生错误。

    数组类型会通过,因为获取类型数组的地址T 会给你T (*)[],也就是指向数组的指针,而不是指向指针的指针。这意味着模板版本check_type不匹配。感谢SFINAE,编译器将继续使用 的非模板版本check_type,它应该接受任何一对指针。由于非模板化版本的返回类型已完全定义,因此不会产生错误。而且由于我们现在不处理模板,因此本地类型可以正常工作。

于 2011-06-06T17:56:07.537 回答
4

如果您在仅 Microsoft 的平台上,则可以利用_countof宏。这是一个非标准扩展,它将返回数组中元素的计数。与大多数 countof 样式宏相比,它的优势在于,如果将其用于非数组类型,则会导致编译错误。

以下工作正常(VS 2008 RTM)

static int ARRAY[5];
enum ENUM{N=_countof(ARRAY)};

但再一次,它是特定于 MS 的,所以这可能不适合你。

于 2009-09-30T20:10:40.960 回答
3

一般来说,你无法解决它,这就是像boost 数组这样的数组包装器的原因之一(当然还有 stl 风格的行为)。

于 2009-09-30T20:12:49.490 回答
2

如果没有当前 C++ 标准的宏,似乎不可能将 sizeof 数组作为编译时常量获取(您需要一个函数来推断数组大小,但在需要编译时常量的地方不允许调用函数) . [编辑:但请看 Minaev 的绝妙解决方案!]

但是,您的模板版本也不是类型安全的,并且遇到与宏相同的问题:它还接受指针,尤其是衰减为指针的数组。当它接受一个指针时,sizeof(T*) / sizeof(T) 的结果没有意义。

更好的:

template <typename T, size_t N>
size_t sizeof_array(T (&)[N]){
    return N;
}
于 2009-09-30T20:38:18.100 回答
2

如果没有 C++0x,我能得到的最接近的是:

#include <iostream>

template <typename T>
struct count_of_type
{
};


template <typename T, unsigned N>
struct count_of_type<T[N]> 
{
    enum { value = N };
};

template <typename T, unsigned N>
unsigned count_of ( const T (&) [N] )
{
    return N;
};


int main ()
{
    std::cout << count_of_type<int[20]>::value << std::endl;
    std::cout << count_of_type<char[42]>::value << std::endl;

    // std::cout << count_of_type<char*>::value << std::endl; // compile error

    int foo[1234];

    std::cout << count_of(foo) << std::endl;

    const char* bar = "wibble";

    // std::cout << count_of( bar ) << std::endl; // compile error

    enum E1 { N = count_of_type<int[1234]>::value } ;

    return 0;
}

它要么给你一个可以传递变量的函数,要么给你一个模板,你也可以传递类型。您不能将该函数用于编译时间常数,但大多数情况下您知道类型,即使仅作为模板参数也是如此。

于 2009-09-30T20:53:19.987 回答
2

现在 STL 库可用于决定/选择数组大小的编译时间

#include <iostream>
#include <array>

template<class T>
void test(T t)
{
    int a[std::tuple_size<T>::value]; // can be used at compile time
    std::cout << std::tuple_size<T>::value << '\n';
}

int main()
{
    std::array<float, 3> arr;
    test(arr);
}

输出:3

于 2017-08-01T00:49:48.367 回答