Compilation time counter in the template class

I have a compile time counter that I have used for many years, inspired by these answers . It works in C ++ 03/11, and as far as I checked it is relatively good on the main compilers:

namespace meta { template<unsigned int n> struct Count { char data[n]; }; template<int n> struct ICount : public ICount<n-1> {}; template<> struct ICount<0> {}; #define MAX_COUNT 64 #define MAKE_COUNTER( _tag_ ) \ static ::meta::Count<1> _counter ## _tag_ (::meta::ICount<1>) #define GET_COUNT( _tag_ ) \ (sizeof(_counter ## _tag_ (::meta::ICount<MAX_COUNT + 1>())) - 1) #define INC_COUNT( _tag_ ) \ static ::meta::Count<GET_COUNT(_tag_) + 2> _counter ## _tag_ (::meta::ICount<2 + GET_COUNT(_tag_)>) } 

The following test compiles and works fine (expected result 0 1 2 3 ):

 struct Test { MAKE_COUNTER( uu ); static const unsigned int a = GET_COUNT( uu ); INC_COUNT( uu ); static const unsigned int b = GET_COUNT( uu ); INC_COUNT( uu ); static const unsigned int c = GET_COUNT( uu ); INC_COUNT( uu ); static const unsigned int d = GET_COUNT( uu ); }; template<typename T> void test() { std::cout << T::a << " " << T::b << " " << T::c << " " << T::d << "\n"; } int main() { test<Test>(); } 

However, I found that I see very strange behavior happening with clang and gcc. If you change Test as a template structure, take an int, for example ( template<int> struct Test and test<Test<42> >() in main ), clang and gcc cannot both compile , complaining that I override the counter function (in while msvc compiles it without problems). For some reason, the compiler cannot calculate sizeof in the template class.

clang find the error on the third INC_COUNT , and gcc find it on the second.

I manually extended this macro and:

  • for clang, he gives

     static ::meta::Count<GET_COUNT(uu)+2> _counteruu(::meta::ICount<(sizeof(_counteruu(::meta::ICount<65>())) - 1)+2>); // ^ ^ 

    removing underlined parentheses solves the problem.

  • for gcc: moving +2 before sizeof is the only workaround

It is sad to note that these workarounds do not seem to work when included in macros. He, as a compiler, just forgets how to calculate the sizeof result after some time ...

Why is this happening? Am I doing something wrong, or are these just compiler errors (since clang and gcc don't even report the same line)?

Note. I know there is a gcc error about this counter . The question is not this error.

+5
source share
1 answer

Your code is poorly formed, no diagnostics are required. ยง3.3.7 / 1, second marker point 1 :

The name N used in class S must refer to the same declaration in its context and when reassessed in completed area S no, a violation of this rule requires diagnostics.

You use overload permission to select the appropriate _counteruu overload. However, in the initializer, for example, a , an overload (= declaration) is selected, which will not be selected if we must enable the overload at the end of Test , for example, in the initializer d . Consequently, _counteruu refers to another, excellent ad when re-evaluating in the completed Test .

To show what kind of calls I have in mind, consider the pre-processed definition of Test :

 struct Test { // (1) static ::meta::Count<1> _counteruu (::meta::ICount<1>); static const unsigned int a = (sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1); // (2) static ::meta::Count<(sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1) + 2> _counteruu (::meta::ICount<(sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1) + 2>); static const unsigned int b = (sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1); // (3) static ::meta::Count<(sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1) + 2> _counteruu (::meta::ICount<(sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1) + 2>); static const unsigned int c = (sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1); // (4) static ::meta::Count<(sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1) + 2> _counteruu (::meta::ICount<(sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1) + 2>); static const unsigned int d = (sizeof(_counteruu (::meta::ICount<64 + 1>())) - 1); }; 

Simplification gives

 struct Test { // (1) static ::meta::Count<1> _counteruu (::meta::ICount<1>); static const unsigned int a = (sizeof(_counteruu (::meta::ICount<65>())) - 1); // (2) static ::meta::Count<2> _counteruu (::meta::ICount<2>); static const unsigned int b = (sizeof(_counteruu (::meta::ICount<65>())) - 1); // (3) static ::meta::Count<3> _counteruu (::meta::ICount<3>); static const unsigned int c = (sizeof(_counteruu (::meta::ICount<65>())) - 1); // (4) static ::meta::Count<4> _counteruu (::meta::ICount<4>); static const unsigned int d = (sizeof(_counteruu (::meta::ICount<65>())) - 1); }; 

We can clearly see how the mechanism works: overload resolution will be preferable to the last added overload when ICount< some large enough number is transferred > due to how the base-based conversions are ranked. However, a call in initializer a will select the first overload; But re-evaluating this initializer will choose the latter.


1 This marker point also existed in C ++ 03, but in section 3.3.6.

+7
source

All Articles