5

I have a compile-time counter that I used for years, inspired by these answers. It works in C++03/11, and as far as I tested, relatively well on major 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 runs perfectly (expected output is 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 a case were I see a very strange behavior happening with clang and gcc. If you change Test to be a template struct, taking an int for example (template<int> struct Test, and test<Test<42> >() in main), clang and gcc both fail to compile, complaining that I am redefining the counter function (while msvc compiles it without problems). For some reason the compiler fails to compute a sizeof in a template class.

clang find the error at the third INC_COUNT, while gcc find it at the second one.

I manually expanded this macro, and:

  • for clang, it gives

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

    removing the underlined parentheses solves the issue.

  • for gcc: moving the +2 before the sizeof is the only work-around

The sad note is that these workarounds seem not to work when included in the macros. It's like the compiler just forgets how to compute the result of sizeof after some time...

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

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

Community
  • 1
  • 1
Synxis
  • 9,236
  • 2
  • 42
  • 64
  • (Ignore all my earlier comments, which I have now deleted. They were incorrect. I had manually expanded some of the templates and didn't quite remember correctly what experiments I had done.) – Aaron McDaid Jul 30 '15 at 12:19

1 Answers1

7

Your code is ill-formed, no diagnostic required. §3.3.7/1, second bullet point1:

A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.

You use overload resolution to select the appropriate overload of _counteruu. However, in the initializer of e.g. a, an overload (=declaration) is selected that wouldn't be selected if we were to perform overload resolution at the end of Test, such as in the initializer of d. Hence _counteruu refers to another, distinct declaration when re-evaluated in the completed scope of Test.

To show up which calls exactly I'm referring to, consider the preprocessed 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 yields

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 now: Overload resolution will prefer the last added overload when ICount<some sufficiently large number> is passed due to the way derived-to-base conversions are ranked. However, the call in the initializer of a will select the first overload; But re-evaluating this initializer would select the last one.


1 This bullet point existed in C++03 as well, but in §3.3.6.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Ok, I didn't know this point, so this code was always ill-formed. It is interesting that clang still allows one "redefinition" before throwing an error. – Synxis Jul 30 '15 at 12:41