Can this macro be converted to a function?

后端 未结 16 1207
后悔当初
后悔当初 2020-12-05 16:27

While refactoring code and ridding myself of all those #defines that we\'re now taught to hate, I came across this beauty used to calculate the number of elements in a struc

相关标签:
16条回答
  • 2020-12-05 17:09

    As JohnMcG's answer, but

    Disadvantage is you will have a copy of this in your binary for every Typename, Size combination.

    That's why you'd make it an inline template function.

    0 讨论(0)
  • 2020-12-05 17:10

    I don't think that that really does work out the number of elements in a structure. If the structure is packed and you used things smaller than the pointer size (such as char on a 32-bit system) then your results are wrong. Also, if the struct contains a struct you are wrong too!

    0 讨论(0)
  • 2020-12-05 17:11

    None has so far proposed a portable way to get the size of an array when you only have an instance of an array and not its type. (typeof and _countof is not portable so can't be used.)

    I'd do it the following way:

    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
    }
    
    • It works when only the value is around.
    • It is portable and only uses std-C++.
    • It fails with a descriptiv error message.
    • It does not evaluate the value. (I can't think up of a situation where this would be a problem because array type can't be returned by a function, but better be safe than sorry.)
    • It returns the size as compiletime constant.

    I wrapped the construct into a macro to have some decent syntax. If you want to get rid of it your only option is to do the substitution manually.

    0 讨论(0)
  • 2020-12-05 17:11

    Your macro is misnamed, it should be called ARRAYSIZE. It is used to determine the number of elements in an array whos size is fixed at compile time. Here's a way it can work:

    char foo[ 128 ]; // In reality, you'd have some constant or constant expression as the array size.

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

    It's kind of brittle to use, because you can make this mistake:

    char* foo = new char[128];

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

    You will now iterate for i = 0 to < 1 and tear your hair out.

    0 讨论(0)
  • 2020-12-05 17:15

    The type of a template function is inferred automatically, in contrast with that of a template class. You can use it even simpler:

    template< typename T > size_t structsize( const T& t ) { 
      return sizeof( t ) / sizeof( *t ); 
    }
    
    
    int ints[] = { 1,2,3 };
    assert( structsize( ints ) == 3 );
    

    But I do agree it doesn't work for structs: it works for arrays. So I would rather call it Arraysize :)

    0 讨论(0)
  • 2020-12-05 17:16

    KTC's solution is clean but it can't be used at compile-time and it is dependent on compiler optimization to prevent code-bloat and function call overhead.

    One can calculate array size with a compile-time-only metafunction with zero runtime cost. BCS was on the right track but that solution is incorrect.

    Here's my solution:

    // 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 ) 
    

    with test code (using Boost.StaticAssert to demonstrate compile-time-only usage):

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

    This solution rejects non-array types at compile time so it will not get confused by pointers as the macro version does.

    0 讨论(0)
提交回复
热议问题