这个宏可以转换为函数吗?

时间:2008-09-18 18:40:35

标签: c++ macros c-preprocessor

在重构代码并摆脱我们现在被教导讨厌的所有#defines时,我偶然发现了这种用于计算结构中元素数量的美:

#define STRUCTSIZE(s) (sizeof(s) / sizeof(*s))

非常有用,但可以将其转换为内联函数或模板吗?

好的,ARRAYSIZE会是一个更好的名字,但这是遗留代码(不知道它来自哪里,至少15年)所以我按原样粘贴它。

16 个答案:

答案 0 :(得分:19)

如上所述,代码实际上计算出数组中元素的数量,而不是结构。我会在需要时明确写出sizeof()除法。如果我要使它成为一个函数,我想在其定义中明确表示它期望一个数组。

template<typename T,int SIZE>
inline size_t array_size(const T (&array)[SIZE])
{
    return SIZE;
}

以上类似于xtofl's,除了它防止传递一个指针(指向动态分配的数组)并错误地得到错误的答案。

编辑:根据JohnMcG简化。 编辑:内联。

不幸的是,上面没有提供编译时答案(即使编译器内联并优化它是引擎盖下的常量),因此不能用作编译时常量表达式。即它不能用作声明静态数组的大小。在C ++ 0x下,如果用 constexpr 替换关键字 inline (constexpr隐式内联),这个问题就会消失。

constexpr size_t array_size(const T (&array)[SIZE])

jwfearn's解决方案适用于编译时,但涉及到一个typedef,它在新名称的声明中有效地“保存”了数组大小。然后通过使用新名称初始化常量来计算数组大小。在这种情况下,也可以从一开始就将数组大小简单地保存为常量。

Martin York's发布的解决方案也可以在编译时工作,但涉及使用非标准的 typeof()运算符。解决这个问题的方法是等待C ++ 0x并使用 decltype (到那时我们不会真正需要它来解决这个问题,因为我们有 constexpr )。另一种方法是使用Boost.Typeof,在这种情况下我们最终会使用

#include <boost/typeof/typeof.hpp>

template<typename T>
struct ArraySize
{
    private:    static T x;
    public:     enum { size = sizeof(T)/sizeof(*x)};
};
template<typename T>
struct ArraySize<T*> {};

并通过写作

使用
ArraySize<BOOST_TYPEOF(foo)>::size

其中 foo 是数组的名称。

答案 1 :(得分:5)

KTC的解决方案是干净的,但它不能在编译时使用,它依赖于编译器优化来防止代码膨胀和函数调用开销。

可以使用仅编译时元函数计算数组大小,运行时成本为零。 BCS处于正确的轨道上但该解决方案不正确。

这是我的解决方案:

// asize.hpp
template < typename T >
struct asize; // no implementation for all types...

template < typename T, size_t N >
struct asize< T[N] > { // ...except arrays
    static const size_t val = N;
};

template< size_t N  >
struct count_type { char val[N]; };

template< typename T, size_t N >
count_type< N > count( const T (&)[N] ) {}

#define ASIZE( a ) ( sizeof( count( a ).val ) ) 
#define ASIZET( A ) ( asize< A >::val ) 

使用测试代码(使用Boost.StaticAssert来演示仅编译时使用):

// asize_test.cpp
#include <boost/static_assert.hpp>
#include "asize.hpp"

#define OLD_ASIZE( a ) ( sizeof( a ) / sizeof( *a ) )

typedef char C;
typedef struct { int i; double d; } S;
typedef C A[42];
typedef S B[42];
typedef C * PA;
typedef S * PB;

int main() {
    A a; B b; PA pa; PB pb;
    BOOST_STATIC_ASSERT( ASIZET( A ) == 42 );
    BOOST_STATIC_ASSERT( ASIZET( B ) == 42 );
    BOOST_STATIC_ASSERT( ASIZET( A ) == OLD_ASIZE( a ) );
    BOOST_STATIC_ASSERT( ASIZET( B ) == OLD_ASIZE( b ) );
    BOOST_STATIC_ASSERT( ASIZE( a ) == OLD_ASIZE( a ) );
    BOOST_STATIC_ASSERT( ASIZE( b ) == OLD_ASIZE( b ) );
    BOOST_STATIC_ASSERT( OLD_ASIZE( pa ) != 42 ); // logic error: pointer accepted
    BOOST_STATIC_ASSERT( OLD_ASIZE( pb ) != 42 ); // logic error: pointer accepted
 // BOOST_STATIC_ASSERT( ASIZE( pa ) != 42 ); // compile error: pointer rejected
 // BOOST_STATIC_ASSERT( ASIZE( pb ) != 42 ); // compile error: pointer rejected
    return 0;
}

此解决方案在编译时拒绝非数组类型,因此不会像宏版本那样被指针混淆。

答案 2 :(得分:5)

当你只有一个数组的实例而不是它的类型时,目前还没有提出一种可移植的方法来获取数组的大小。 (typeof和_countof不可移植,因此无法使用。)

我会按照以下方式进行:

template<int n>
struct char_array_wrapper{
    char result[n];
};

template<typename T, int s>
char_array_wrapper<s> the_type_of_the_variable_is_not_an_array(const T (&array)[s]){
}


#define ARRAYSIZE_OF_VAR(v) sizeof(the_type_of_the_variable_is_not_an_array(v).result)

#include <iostream>
using namespace std;

int main(){
    int foo[42];
    int*bar;
    cout<<ARRAYSIZE_OF_VAR(foo)<<endl;
    // cout<<ARRAYSIZE_OF_VAR(bar)<<endl;  fails
}
  • 仅在值附近时才有效。
  • 它是可移植的,只使用std-C ++。
  • 失败并显示描述性错误消息。
  • 它不会评估该值。 (我想不出这会出现问题的情况,因为函数不能返回数组类型,但最好是安全而不是抱歉。)
  • 它返回编译时常量的大小。

我将构造包装到宏中以获得一些不错的语法。如果你想摆脱它,你唯一的选择是手动进行替换。

答案 3 :(得分:2)

宏有一个非常具有误导性的名称 - 如果将数组的名称作为宏参数传入,宏中的表达式将返回数组中的元素数。

对于其他类型,如果类型是指针或者你会得到语法错误,你会得到或多或少无意义的东西。

通常,该宏的名称类似于NUM_ELEMENTS()或其他东西,以表明其真正的用处。用C中的函数替换宏是不可能的,但在C ++中可以使用模板。

我使用的版本基于Microsoft的winnt.h标头中的代码(如果发布此片段超出合理使用,请告诉我们):

//
// Return the number of elements in a statically sized array.
//   DWORD Buffer[100];
//   RTL_NUMBER_OF(Buffer) == 100
// This is also popularly known as: NUMBER_OF, ARRSIZE, _countof, NELEM, etc.
//
#define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0]))

#if defined(__cplusplus) && \
    !defined(MIDL_PASS) && \
    !defined(RC_INVOKED) && \
    !defined(_PREFAST_) && \
    (_MSC_FULL_VER >= 13009466) && \
    !defined(SORTPP_PASS)
//
// 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)))

//
// This does not work with:
//
// void Foo()
// {
//    struct { int x; } y[2];
//    RTL_NUMBER_OF_V2(y); // illegal use of anonymous local type in template instantiation
// }
//
// You must instead do:
//
// struct Foo1 { int x; };
//
// void Foo()
// {
//    Foo1 y[2];
//    RTL_NUMBER_OF_V2(y); // ok
// }
//
// OR
//
// void Foo()
// {
//    struct { int x; } y[2];
//    RTL_NUMBER_OF_V1(y); // ok
// }
//
// OR
//
// void Foo()
// {
//    struct { int x; } y[2];
//    _ARRAYSIZE(y); // ok
// }
//

#else
#define RTL_NUMBER_OF_V2(A) RTL_NUMBER_OF_V1(A)
#endif

#ifdef ENABLE_RTL_NUMBER_OF_V2
#define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V2(A)
#else
#define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V1(A)
#endif

//
// ARRAYSIZE is more readable version of RTL_NUMBER_OF_V2, and uses
// it regardless of ENABLE_RTL_NUMBER_OF_V2
//
// _ARRAYSIZE is a version useful for anonymous types
//
#define ARRAYSIZE(A)    RTL_NUMBER_OF_V2(A)
#define _ARRAYSIZE(A)   RTL_NUMBER_OF_V1(A)

另外,Matthew Wilson的书“Imperfect C ++”对这里发生的事情有一个很好的处理(第14.3节 - 第211-213页 - 数组和指针 - dimensionof())。

答案 4 :(得分:1)

  • 功能,没有模板功能,是
  • 模板,我想是的(但是C ++
  • 模板不是我的事情)

修改:来自Doug的代码

template <typename T>
uint32_t StructSize()  // This might get inlined to a constant at compile time
{
   return sizeof(T)/sizeof(*T);
}

// or to get it at compile time for shure

class StructSize<typename T>
{
   enum { result = sizeof(T)/sizeof(*T) };
}

我被告知第二个不起作用。 OTOH之类的东西应该是可行的,我只是不使用C ++来修复它。

A page on C++ (and D) templates for compile time stuff

答案 5 :(得分:1)

您的宏名称错误,应该称为ARRAYSIZE。它用于确定在编译时大小固定的数组中的元素数。这是一种可行的方式:

  

char foo [128]; //实际上,你是   有一些常数或常数   表达式作为数组大小。

     

for(unsigned i = 0; i&lt; STRUCTSIZE(   foo); ++ i){}

使用起来很脆弱,因为你可以犯这个错误:

  

char * foo = new char [128];

     

for(unsigned i = 0; i&lt; STRUCTSIZE(   foo); ++ i){}

现在,您将迭代i = 0到&lt; 1,撕掉你的头发。

答案 6 :(得分:1)

模板函数的类型是自动推断的,与模板类的类型相反。您可以更简单地使用它:

template< typename T > size_t structsize( const T& t ) { 
  return sizeof( t ) / sizeof( *t ); 
}


int ints[] = { 1,2,3 };
assert( structsize( ints ) == 3 );

但我同意它不适用于结构:它适用于数组。所以我宁愿称它为Arraysize:)

答案 7 :(得分:1)

简化@KTC,因为我们在模板参数中有数组的大小:

template<typename T, int SIZE>
int arraySize(const T(&arr)[SIZE])
{
    return SIZE;
}

缺点是,对于每个Typename,Size组合,您将在二进制文件中获得此副本。

答案 8 :(得分:1)

我更喜欢[BCS]建议的枚举方法(在Can this macro be converted to a function?中)

这是因为您可以在编译器期望编译时常量的地方使用它。该语言的当前版本不允许您将函数结果用于编译时间,但我相信这将在下一版本的编译器中出现:

这种方法的问题在于,当它与一个重载'*'运算符的类一起使用时,它不会产生编译时错误(详见下面的代码)。

不幸的是,'BCS'提供的版本没有按预期编译,所以这是我的版本:

#include <iterator>
#include <algorithm>
#include <iostream>


template<typename T>
struct StructSize
{
    private:    static T x;
    public:      enum { size = sizeof(T)/sizeof(*x)};
};

template<typename T>
struct StructSize<T*>
{
    /* Can only guarantee 1 item (maybe we should even disallow this situation) */
    //public:     enum { size = 1};
};

struct X
{
    int operator *();
};


int main(int argc,char* argv[])
{
    int data[]                                  = {1,2,3,4,5,6,7,8};
    int copy[ StructSize<typeof(data)>::size];

    std::copy(&data[0],&data[StructSize<typeof(data)>::size],&copy[0]);
    std::copy(&copy[0],&copy[StructSize<typeof(copy)>::size],std::ostream_iterator<int>(std::cout,","));

    /*
     * For extra points we should make the following cause the compiler to generate an error message */
    X   bad1;
    X   bad2[StructSize<typeof(bad1)>::size];
}

答案 9 :(得分:0)

是的,它可以在C ++中成为模板

template <typename T>
size_t getTypeSize()
{
   return sizeof(T)/sizeof(*T);
}

使用:

struct JibbaJabba
{
   int int1;
   float f;
};

int main()
{
    cout << "sizeof JibbaJabba is " << getTypeSize<JibbaJabba>() << std::endl;
    return 0;
}

请参阅BCS上面或下面的帖子,了解在编译时使用一些轻模板元编程实现此类的一种很酷的方法。

答案 10 :(得分:0)

我认为这确实不能解决结构中元素的数量问题。如果结构被打包并且您使用的东西小于指针大小(例如32位系统上的char),则结果是错误的。另外,如果结构包含结构,那你也错了!

答案 11 :(得分:0)

xtofl有正确的答案来查找数组大小。

可能不需要宏或模板来查找结构的大小,因为sizeof()应该很好。

我同意preprocessor is evil,但有时它是least evil of the alternatives

答案 12 :(得分:0)

作为JohnMcG的回答,但是

缺点是,对于每个Typename,Size组合,您将在二进制文件中获得此副本。

这就是为什么你要使它成为内嵌模板功能。

答案 13 :(得分:0)

答案 14 :(得分:0)

Windows特定:

CRT提供的宏_countof()正是为此目的。

A link to the doc at MSDN

答案 15 :(得分:0)

对于C99风格的可变长度数组,似乎纯宏方法(sizeof(arr)/ sizeof(arr [0]))是唯一可行的方法。