我有一个编译时计数器,我使用了多年,灵感来自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 Test
,clang 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。问题不在于这个错误。
答案 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中。