Compile-time counter in template class

◇◆丶佛笑我妖孽 提交于 2021-02-08 14:57:59

问题


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.


回答1:


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.



来源:https://stackoverflow.com/questions/31720516/compile-time-counter-in-template-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!