简体   繁体   English

不使用宏编译时间sizeof_array

[英]Compile time sizeof_array without using a macro

This is just something that has bothered me for the last couple of days, I don't think it's possible to solve but I've seen template magic before. 这只是过去几天困扰我的事情,我认为不可能解决,但我之前看过模板魔术。

Here goes: 开始:

To get the number of elements in a standard C++ array I could use either a macro (1), or a typesafe inline function (2): 要获得标准C ++数组中的元素数量,我可以使用宏(1)或类型安全内联函数(2):

(1) (1)

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

(2) (2)

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

As you can see, the first one has the problem of being a macro (for the moment I consider that a problem) and the other one has the problem of not being able to get the size of an array at compile time; 正如您所看到的,第一个问题是成为一个宏(目前我认为是一个问题),而另一个问题是在编译时无法获得数组的大小; ie I can't write: 即我不能写:

enum ENUM{N=sizeof_array(ARRAY)};

or 要么

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

Does anyone know if this can be solved? 有谁知道这是否可以解决?

Update : 更新

This question was created before constexpr was introduced. 这个问题是在引入constexpr之前创建的。 Nowadays you can simply use: 现在你可以简单地使用:

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

Try the following from here : 这里尝试以下内容:

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

It should print out: "The array count is: 10" 它应该打印出来:“数组计数是:10”

In C++1x constexpr will get you that: 在C ++中,1x constexpr将为您提供:

template <typename T, size_t N>
constexpr size_t countof(T(&)[N])
{
    return N;
}

The best I can think of is this: 我能想到的最好的是:

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];

which has to be used with another sizeof : 必须与另一个sizeof一起使用:

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

[EDIT] [编辑]

Come to think of it, I believe this is provably impossible to do in a single "function-like call" in C++03, apart from macros, and here's why. 想想看,我相信除了宏之外,在C ++ 03中单独的“类似函数的调用”这是不可能做到的,这就是原因。

On one hand, you will clearly need template parameter deduction to obtain size of array (either directly, or via sizeof as you do). 一方面,您显然需要模板参数推导来获得数组的大小(直接或通过sizeof )。 However, template parameter deduction is only applicable to functions, and not to classes; 但是,模板参数推导仅适用于函数,而不适用于类; ie you can have a template parameter R of type reference-to-array-of-N, where N is another template parameter, but you'll have to provide both R and N at the point of the call; 即你可以有一个类型为reference-to-array-of-N的模板参数R,其中N是另一个模板参数,但是你必须在调用点提供R和N. if you want to deduce N from R, only a function call can do that. 如果你想从R中推导出N,那么只有函数调用才能做到。

On the other hand, the only way any expression involving a function call can be constant is when it's inside sizeof . 另一方面,任何涉及函数调用的表达式都可以是常量的唯一方法是它在sizeof内部。 Anything else (eg accessing a static or enum member on return value of function) still requires the function call to occur, which obviously means this won't be a constant expression. 其他任何东西(例如,在函数的返回值上访问静态或枚举成员)仍然需要进行函数调用,这显然意味着这不是常量表达式。

The Problem 问题

I like Adisak's answer : 我喜欢Adisak的回答

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

It's what Microsoft uses for the _countof macro in VS2008, and it's got some nice features: 这是微软在VS2008中用于_countof宏的东西,它有一些不错的功能:

  • It operates at compile time 它在编译时运行
  • It's typesafe (ie it will generate a compile-time error if you give it a pointer, which arrays degrade too all too easily) 它是类型安全的 (即如果你给它一个指针,它将产生编译时错误,这些数组也很容易降级)

But as pointed out by Georg , this approach uses templates, so it's not guaranteed to work with local types for C++03 : 但正如Georg指出的那样,这种方法使用模板,所以不保证可以使用C ++ 03的本地类型

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

Fortunately, we're not out luck. 幸运的是,我们没有运气。

The Solution 解决方案

Ivan Johnson came up with a clever approach that wins on all accounts: it's typesafe, compile-time, and works with local types: 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*);
};

For those who are interested, it works by inserting two "tests" before the standard sizeof-based array-size macro. 对于那些感兴趣的人,它通过在标准的基于sizeof的数组大小宏之前插入两个“测试”来工作。 Those tests don't impact the final calculation, but are designed to generate compile errors for non-array types: 这些测试不影响最终计算,但旨在为非数组类型生成编译错误:

  1. The first test fails unless arr is integral, enum, pointer, or array. 除非arr是整数,枚举,指针或数组,否则第一次测试失败。 reinterpret_cast<const T*> should fail for any other types. 对于任何其他类型, reinterpret_cast<const T*>都应该失败。
  2. The second test fails for integral, enum, or pointer types. 对于整数,枚举或指针类型,第二次测试失败。

    Integral and enum types will fail because there's no version of check_type that they match, since check_type expects pointers. 积分和枚举类型将失败,因为它们没有匹配的check_type版本,因为check_type需要指针。

    Pointer types will fail because they'll match the templated version of check_type , but the return type ( Is_pointer ) for the templated check_type is incomplete, which will produce an error. 指针类型将失败,因为他们会匹配的模板版本check_type ,但返回类型( Is_pointer为模板) check_type是不完整的,这将产生一个错误。

    Array types will pass because taking the address of an array of type T will give you T (*)[] , aka a pointer-to-an-array, not a pointer-to-a-pointer. 数组类型将通过,因为获取类型为T的数组的地址将为您提供T (*)[] ,也就是指向数组的指针,而不是指向指针的指针。 That means that the templated version of check_type won't match. 这意味着check_type的模板化版本将不匹配。 Thanks to SFINAE , the compiler will move on to the non-templated version of check_type , which should accept any pair of pointers. 感谢SFINAE ,编译器将继续使用非模板化版本的check_type ,它应该接受任何一对指针。 Since the return type for the non-templated version is defined completely, no error will be produced. 由于完全定义了非模板化版本的返回类型,因此不会产生错误。 And since we're not dealing with templates now, local types work fine. 由于我们现在不处理模板,因此本地类型工作正常。

It's not exactly what you're looking for, but it's close - a snippet from winnt.h which includes some explanation of what the #$%^ it's doing: 这不是你想要的,但它很接近 - 来自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)))

The RTL_NUMBER_OF_V2() macro ends up being used in the more readable ARRAYSIZE() macro. RTL_NUMBER_OF_V2()宏最终用于更易读的ARRAYSIZE()宏。

Matthew Wilson's "Imperfect C++" book also has a discussion of the techniques that are used here. 马修威尔逊的“不完美的C ++”一书也讨论了这里使用的技术。

If you are on a Microsoft only platform, you can take advantage of the _countof macro. 如果您使用的是Microsoft平台,则可以利用_countof宏。 This is a non-standard extension which will return the count of elements within an array. 这是一个非标准扩展,它将返回数组中元素的数量。 It's advantage over most countof style macros is that it will cause a compilation error if it's used on a non-array type. 它比大多数countof样式宏的优点是,如果它在非数组类型上使用,它将导致编译错误。

The following works just fine (VS 2008 RTM) 以下工作正常(VS 2008 RTM)

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

But once again, it's MS specific so this may not work for you. 但再一次,它是MS特定的,所以这可能不适合你。

你不能解决它,这就是数组包装器的原因之一,如boost数组 (当然还有stl风格的行为)。

It appears not to be possible to obtain the sizeof array as a compile-time constant without a macro with current C++ standard (you need a function to deduce the array size, but function calls are not allowed where you need a compile-time constant). 在没有具有当前C ++标准的宏的情况下,似乎无法将sizeof数组作为编译时常量获取(您需要一个函数来推导数组大小,但在需要编译时常量的情况下不允许函数调用) 。 [Edit: But see Minaev's brilliant solution!] [编辑:但看看Minaev的精彩解决方案!]

However, your template version isn't typesafe either and suffers from the same problem as the macro: it also accepts pointers and notably arrays decayed to a pointer. 但是,您的模板版本也不是类型安全的,并且遇到与宏相同的问题:它还接受指针,特别是数组衰减为指针。 When it accepts a pointer, the result of sizeof(T*) / sizeof(T) cannot be meaningful. 当它接受指针时,sizeof(T *)/ sizeof(T)的结果无意义。

Better: 更好:

template <typename T, size_t N>
size_t sizeof_array(T (&)[N]){
    return N;
}

Without C++0x, the closest I can get is: 没有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;
}

which either gives you a function you can pass the variable to, or a template you can pass the type too. 它既可以为您提供可以传递变量的函数,也可以为您传递类型的模板。 You can't use the function for a compile time constant, but most cases you know the type, even if only as template parameter. 您不能将该函数用于编译时常量,但大多数情况下您知道该类型,即使仅作为模板参数。

Now STL libraries are available to decide/select array size compile time 现在可以使用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);
}

Output: 3 输出:3

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM