模板类中的编译时计数器

Syn*_*xis 5 c++ gcc templates clang

我有一个编译时计数器,我使用多年,受这些答案的启发.它适用于C++ 03/11,就我测试而言,在主要编译器上相对较好:

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_)>)
}
Run Code Online (Sandbox Code Playgroud)

以下测试编译并运行完美(预期输出为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>();
}
Run Code Online (Sandbox Code Playgroud)

然而,我发现一个案例,我发现clang和gcc发生了一种非常奇怪的行为.如果您更改Test为模板结构,以int为例(template<int> struct Test,并且test<Test<42> >()main),clang和gcc都无法编译,抱怨我正在重新定义计数器函数(而msvc编译它没有问题).由于某种原因,编译器无法计算模板类中的sizeof.

clang在第三个找到错误INC_COUNT,而gcc在第二个找到它.

我手动扩展了这个宏,并且:

  • 对于铿锵,它给出了

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

    删除带下划线的括号可以解决问题.

  • 对于gcc:移动+2之前sizeof是唯一的解决方法

遗憾的是,当包含在宏中时,这些变通办法似乎无法工作.这就像编译器忘记了一段时间后如何计算sizeof的结果......

为什么会这样?我做错了什么,或者只是编译器错误(因为clang和gcc甚至不报告相同的行)?

注意:我知道这个计数器有一个gcc错误.问题不在于这个错误.

Col*_*mbo 7

您的代码格式错误,无需诊断.§3.3.7/ 1,第二点要点1:

N类中使用的名称S应在其上下文中引用相同的声明,并在完成的范围内重新评估S.违反此规则无需诊断.

您使用重载决策来选择适当的重载_counteruu.但是,在例如初始化器中a,如果我们要在结束时执行重载决策Test,例如在初始化器中,则选择不会被选择的重载(=声明)d.因此_counteruu,在完成的范围内重新评估时,指的是另一个不同的声明Test.

要显示我所指的是哪些调用,请考虑以下预处理定义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);

};
Run Code Online (Sandbox Code Playgroud)

简化收益率

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);
};
Run Code Online (Sandbox Code Playgroud)

我们可以清楚地看到该机制现在如何工作:由于派生到基础转换的排序方式,当ICount<一些足够大的数字>被传递时,重载分辨率将更喜欢最后添加的重载.但是,初始化程序中的调用a将选择第一个重载; 但重新评估此初始化程序将选择最后一个.


1这个要点也存在于C++ 03中,但在§3.3.6中.