模板类中的编译时计数器

时间:2015-07-30 09:52:14

标签: c++ templates gcc clang

我有一个编译时计数器,我使用了多年,灵感来自these answers。它适用于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_)>)
}

以下测试compiles and runs perfectly(预期输出为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>();
}

然而,我发现一个案例,我发现clang和gcc发生了一种非常奇怪的行为。如果您将Test更改为模板结构,例如在template<int> struct Testclang and gcc both fail to compile中使用int(test<Test<42> >()main),抱怨我是重新定义计数器功能(而msvc编译它没有问题)。由于某种原因,编译器无法计算模板类中的sizeof。

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

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

  • 对于clang,它给出了

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

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

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

遗憾的是,这些解决方法似乎在包含在宏中时无法正常工作。它就像编译器在一段时间之后忘记了如何计算sizeof的结果......

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

注意:我知道there is a gcc bug about this counter。问题不在于这个错误。

1 个答案:

答案 0 :(得分: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);

};

简化收益

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

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

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