C++ 是否支持编译时计数器?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6166337/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does C++ support compile-time counters?
提问by Potatoswatter
For the purpose of introspection, sometimes I've wanted to automatically assign serial numbers to types, or something similar.
出于内省的目的,有时我想自动将序列号分配给类型或类似的东西。
Unfortunately, template metaprogramming is essentially a functional language, and as such lacks global variables or modifiable state which would implement such a counter.
不幸的是,模板元编程本质上是一种函数式语言,因此缺乏实现这种计数器的全局变量或可修改状态。
Or is it?
或者是吗?
Example code by request:
请求示例代码:
#include <iostream>
int const a = counter_read;
counter_inc;
counter_inc;
counter_inc;
counter_inc;
counter_inc;
int const b = counter_read;
int main() {
std::cout << a << ' ' << b << '\n'; // print "0 5"
counter_inc_t();
counter_inc_t();
counter_inc_t();
std::cout << counter_read << '\n'; // print "8"
struct {
counter_inc_t d1;
char x[ counter_read ];
counter_inc_t d2;
char y[ counter_read ];
} ls;
std::cout << sizeof ls.x << ' ' << sizeof ls.y << '\n'; // print "9 10"
}
采纳答案by Potatoswatter
Well… yes, template metaprogramming lacks side effects as it is intended. I was misled by a bug in older versions of GCC and a little unclear wording in the Standard to believe that all those features were possible.
嗯……是的,模板元编程没有预期的副作用。我被 GCC 旧版本中的一个错误和标准中的一些不清楚的措辞误导,认为所有这些功能都是可能的。
However, at least the namespace-scope functionality can be achieved with little use of templates at all. Function lookup can extract numeric state from the set of declared functions, as demonstrated below.
但是,至少可以在几乎不使用模板的情况下实现命名空间范围的功能。函数查找可以从声明的函数集中提取数字状态,如下所示。
Library code:
图书馆代码:
template< size_t n > // This type returns a number through function lookup.
struct cn // The function returns cn<n>.
{ char data[ n + 1 ]; }; // The caller uses (sizeof fn() - 1).
template< typename id, size_t n, size_t acc >
cn< acc > seen( id, cn< n >, cn< acc > ); // Default fallback case.
/* Evaluate the counter by finding the last defined overload.
Each function, when defined, alters the lookup sequence for lower-order
functions. */
#define counter_read( id ) \
( sizeof seen( id(), cn< 1 >(), cn< \
( sizeof seen( id(), cn< 2 >(), cn< \
( sizeof seen( id(), cn< 4 >(), cn< \
( sizeof seen( id(), cn< 8 >(), cn< \
( sizeof seen( id(), cn< 16 >(), cn< \
( sizeof seen( id(), cn< 32 >(), cn< 0 \
/* Add more as desired; trimmed for Stack Overflow code block. */ \
>() ).data - 1 ) \
>() ).data - 1 ) \
>() ).data - 1 ) \
>() ).data - 1 ) \
>() ).data - 1 ) \
>() ).data - 1 )
/* Define a single new function with place-value equal to the bit flipped to 1
by the increment operation.
This is the lowest-magnitude function yet undefined in the current context
of defined higher-magnitude functions. */
#define counter_inc( id ) \
cn< counter_read( id ) + 1 > \
seen( id, cn< ( counter_read( id ) + 1 ) & ~ counter_read( id ) >, \
cn< ( counter_read( id ) + 1 ) & counter_read( id ) > )
Quick demo (see it run):
快速演示(看它运行):
struct my_cnt {};
int const a = counter_read( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );
int const b = counter_read( my_cnt );
counter_inc( my_cnt );
#include <iostream>
int main() {
std::cout << a << ' ' << b << '\n';
std::cout << counter_read( my_cnt ) << '\n';
}
C++11 Update
C++11 更新
Here is an updated version using C++11 constexpr
in place of sizeof
.
这是使用 C++11constexpr
代替sizeof
.
#define COUNTER_READ_CRUMB( TAG, RANK, ACC ) counter_crumb( TAG(), constant_index< RANK >(), constant_index< ACC >() )
#define COUNTER_READ( TAG ) COUNTER_READ_CRUMB( TAG, 1, COUNTER_READ_CRUMB( TAG, 2, COUNTER_READ_CRUMB( TAG, 4, COUNTER_READ_CRUMB( TAG, 8, \
COUNTER_READ_CRUMB( TAG, 16, COUNTER_READ_CRUMB( TAG, 32, COUNTER_READ_CRUMB( TAG, 64, COUNTER_READ_CRUMB( TAG, 128, 0 ) ) ) ) ) ) ) )
#define COUNTER_INC( TAG ) \
constexpr \
constant_index< COUNTER_READ( TAG ) + 1 > \
counter_crumb( TAG, constant_index< ( COUNTER_READ( TAG ) + 1 ) & ~ COUNTER_READ( TAG ) >, \
constant_index< ( COUNTER_READ( TAG ) + 1 ) & COUNTER_READ( TAG ) > ) { return {}; }
#define COUNTER_LINK_NAMESPACE( NS ) using NS::counter_crumb;
template< std::size_t n >
struct constant_index : std::integral_constant< std::size_t, n > {};
template< typename id, std::size_t rank, std::size_t acc >
constexpr constant_index< acc > counter_crumb( id, constant_index< rank >, constant_index< acc > ) { return {}; } // found by ADL via constant_index
The declarations should be put inside a namespace, and all names used in the macros except counter_crumb
should be fully qualified. The counter_crumb
template is found via ADL association with the constant_index
type.
声明应该放在一个命名空间中,除了宏中使用的所有名称都counter_crumb
应该是完全限定的。该counter_crumb
模板通过与ADL一起被发现constant_index
的类型。
The COUNTER_LINK_NAMESPACE
macro can be used to increment one counter in the scope of multiple namespaces.
该COUNTER_LINK_NAMESPACE
宏可用于在多个命名空间范围内增加一个计数器。
回答by Josh Matthews
I believe both MSVC and GCC support a __COUNTER__
preprocessor token that has a monotonically increasing value substituted in its place.
我相信 MSVC 和 GCC 都支持__COUNTER__
预处理器令牌,该令牌具有单调递增的值替代。
回答by iammilind
I was thinking to solve this problem for quite sometime, and have come up with a very short-clean solution. At least I deserve one upvote to try this out. :))
我想解决这个问题已经有一段时间了,并提出了一个非常简短的解决方案。至少我应该得到一个赞成票来尝试这个。:))
Following library code achieves namespace level functionality. i.e. I am successful to implement counter_read
and counter_inc
; but not the counter_inc_t
(which is incremented inside function because template
classes are not allowed inside function)
以下库代码实现了命名空间级别的功能。即我成功实施counter_read
和counter_inc
;但不是counter_inc_t
(它在函数内部递增,因为template
函数内部不允许使用类)
template<unsigned int NUM> struct Counter { enum { value = Counter<NUM-1>::value }; };
template<> struct Counter<0> { enum { value = 0 }; };
#define counter_read Counter<__LINE__>::value
#define counter_inc template<> struct Counter<__LINE__> { enum { value = Counter<__LINE__-1>::value + 1}; }
This technique uses template meta-programmingand leverages the __LINE__
macro.
See the resultfor the code from your answer.
该技术使用模板元编程并利用__LINE__
宏。见结果从你的答案代码。
回答by Matthieu M.
You could use BOOST_PP_COUNTER
from Boost.Preprocessor.
您可以BOOST_PP_COUNTER
从 Boost.Preprocessor使用。
Advantage: it works even for macros
优点:它甚至适用于宏
Disadvantage: there is only one "counter kind" for the whole program, but the mechanism may be reimplemented for dedicated counters
缺点:整个程序只有一种“计数器种类”,但可能会为专用计数器重新实现该机制
回答by Chartas
Since sharing is caring and I spent a few hours fiddling around with the base example thisside provides I'm going to post my solution as well.
由于共享是关怀,我花了几个小时摆弄这个方面提供的基本示例,我也将发布我的解决方案。
The version linked to in the article has two major downsides. The max number it can count too is very low, due to max recursion depth (usually something around 256). And the time it takes to compile as soon as a count of more than a few hundred has been reached is huge.
文章中链接的版本有两个主要缺点。由于最大递归深度(通常约为 256),它可以计算的最大数量也非常低。并且一旦达到数百个以上,编译所需的时间是巨大的。
By implementing binary search to detect if a flag for a counter has already been set or not, it's possible to massively increase the max count (controllable through MAX_DEPTH) and also improve compile time at the same time. =)
通过实现二进制搜索来检测计数器的标志是否已经设置,可以大量增加最大计数(可通过 MAX_DEPTH 控制)并同时缩短编译时间。=)
Usage example:
用法示例:
static constexpr int a = counter_id();
static constexpr int b = counter_id();
static constexpr int c = counter_id();
#include <iostream>
int main () {
std::cout << "Value a: " << a << std::endl;
std::cout << "Value b: " << b << std::endl;
std::cout << "Value c: " << c << std::endl;
}
Fully working code with example at the end: (Except for clang. See comments.)
带有示例的完整工作代码:(除了 clang。请参阅评论。)
// Number of Bits our counter is using. Lower number faster compile time,
// but less distinct values. With 16 we have 2^16 distinct values.
#define MAX_DEPTH 16
// Used for counting.
template<int N>
struct flag {
friend constexpr int adl_flag(flag<N>);
};
// Used for noting how far down in the binary tree we are.
// depth<0> equales leaf nodes. depth<MAX_DEPTH> equals root node.
template<int N> struct depth {};
// Creating an instance of this struct marks the flag<N> as used.
template<int N>
struct mark {
friend constexpr int adl_flag (flag<N>) {
return N;
}
static constexpr int value = N;
};
// Heart of the expression. The first two functions are for inner nodes and
// the next two for termination at leaf nodes.
// char[noexcept( adl_flag(flag<N>()) ) ? +1 : -1] is valid if flag<N> exists.
template <int D, int N, class = char[noexcept( adl_flag(flag<N>()) ) ? +1 : -1]>
int constexpr binary_search_flag(int, depth<D>, flag<N>,
int next_flag = binary_search_flag(0, depth<D-1>(), flag<N + (1 << (D - 1))>())) {
return next_flag;
}
template <int D, int N>
int constexpr binary_search_flag(float, depth<D>, flag<N>,
int next_flag = binary_search_flag(0, depth<D-1>(), flag<N - (1 << (D - 1))>())) {
return next_flag;
}
template <int N, class = char[noexcept( adl_flag(flag<N>()) ) ? +1 : -1]>
int constexpr binary_search_flag(int, depth<0>, flag<N>) {
return N + 1;
}
template <int N>
int constexpr binary_search_flag(float, depth<0>, flag<N>) {
return N;
}
// The actual expression to call for increasing the count.
template<int next_flag = binary_search_flag(0, depth<MAX_DEPTH-1>(),
flag<(1 << (MAX_DEPTH-1))>())>
int constexpr counter_id(int value = mark<next_flag>::value) {
return value;
}
static constexpr int a = counter_id();
static constexpr int b = counter_id();
static constexpr int c = counter_id();
#include <iostream>
int main () {
std::cout << "Value a: " << a << std::endl;
std::cout << "Value b: " << b << std::endl;
std::cout << "Value c: " << c << std::endl;
}
回答by rendaw
Here's another alternative implementation. https://stackoverflow.com/a/6174263/1190123is probably better, but even after manually working through a couple increments on paper I still don't quite understand the math/filtering.
这是另一种替代实现。 https://stackoverflow.com/a/6174263/1190123可能更好,但即使在纸上手动完成几个增量后,我仍然不太了解数学/过滤。
This uses constexpr function recursion to count the number of non-template declared Highest
functions. __COUNTER__
is used as a generational mechanism to prevent new declarations of Highest
from doing self recursion.
这使用 constexpr 函数递归来计算非模板声明Highest
函数的数量。 __COUNTER__
被用作一种生成机制来防止新的声明Highest
进行自递归。
This only compiles on clang for me (3.3). I'm not sure it's compliant, but I'm hopeful. g++ 4.8 fails due to some unimplemented feature (according to the error). Intel compiler 13 also fails, due to a constexpr bug.
这只对我来说在 clang 上编译(3.3)。我不确定它是否合规,但我充满希望。由于某些未实现的功能(根据错误),g++ 4.8 失败。由于 constexpr 错误,英特尔编译器 13 也失败了。
256 level counter
256级计数器
The maximum count per counter is 250 (CounterLimit). CounterLimit can be increased to 256 unless you implement the LCount stuff below.
每个计数器的最大计数为 250 (CounterLimit)。除非您实现下面的 LCount 内容,否则 CounterLimit 可以增加到 256。
Implementation
执行
#include <iostream>
#include <type_traits>
constexpr unsigned int CounterLimit = 250;
template <unsigned int ValueArg> struct TemplateInt { constexpr static unsigned int Value = ValueArg; };
template <unsigned int GetID, typename, typename TagID>
constexpr unsigned int Highest(TagID, TemplateInt<0>)
{
return 0;
}
template <unsigned int GetID, typename, typename TagID, unsigned int Index>
constexpr unsigned int Highest(TagID, TemplateInt<Index>)
{
return Highest<GetID, void>(TagID(), TemplateInt<Index - 1>());
}
#define GetCount(...) \
Highest<__COUNTER__, void>(__VA_ARGS__(), TemplateInt<CounterLimit>())
#define IncrementCount(TagID) \
template <unsigned int GetID, typename = typename std::enable_if<(GetID > __COUNTER__ + 1)>::type> \
constexpr unsigned int Highest( \
TagID, \
TemplateInt<GetCount(TagID) + 1> Value) \
{ \
return decltype(Value)::Value; \
}
Testing
测试
struct Counter1 {};
struct Counter2 {};
constexpr unsigned int Read0 = GetCount(Counter1);
constexpr unsigned int Read1 = GetCount(Counter1);
IncrementCount(Counter1);
constexpr unsigned int Read2 = GetCount(Counter1);
IncrementCount(Counter1);
constexpr unsigned int Read3 = GetCount(Counter1);
IncrementCount(Counter1);
constexpr unsigned int Read4 = GetCount(Counter1);
IncrementCount(Counter1);
IncrementCount(Counter2);
constexpr unsigned int Read5 = GetCount(Counter1);
constexpr unsigned int Read6 = GetCount(Counter2);
int main(int, char**)
{
std::cout << "Ending state 0: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<0>()) << std::endl;
std::cout << "Ending state 1: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<1>()) << std::endl;
std::cout << "Ending state 2: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<2>()) << std::endl;
std::cout << "Ending state 3: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<3>()) << std::endl;
std::cout << "Ending state 4: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<4>()) << std::endl;
std::cout << "Ending state 5: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<5>()) << std::endl;
std::cout << Read0 << std::endl;
std::cout << Read1 << std::endl;
std::cout << Read2 << std::endl;
std::cout << Read3 << std::endl;
std::cout << Read4 << std::endl;
std::cout << Read5 << std::endl;
std::cout << Read6 << std::endl;
return 0;
}
Output
输出
Ending state 0: 0
Ending state 1: 1
Ending state 2: 2
Ending state 3: 3
Ending state 4: 4
Ending state 5: 4
0
0
1
2
3
4
1
250 * 250 level counter
250*250级计数器
If you want higher values than 256, I think you can combine counters. I did 250 * 250 (although I didn't really test counting past 2). CounterLimit has to be lowered to around 250 for compiler compile time recursion limits. Just to note, this took significantly more time to compile for me.
如果您想要高于 256 的值,我认为您可以组合计数器。我做了 250 * 250(虽然我没有真正测试过 2)。CounterLimit 必须降低到 250 左右才能达到编译器编译时递归限制。请注意,这为我编译花了更多的时间。
Implementation
执行
template <typename, unsigned int> struct ExtraCounter { };
template <unsigned int GetID, typename, typename TagID>
constexpr unsigned int LHighest(TagID)
{
return Highest<GetID, void>(ExtraCounter<TagID, CounterLimit>(), TemplateInt<CounterLimit>()) * CounterLimit +
Highest<GetID, void>(
ExtraCounter<TagID, Highest<GetID, void>(ExtraCounter<TagID , CounterLimit>(), TemplateInt<CounterLimit>())>(),
TemplateInt<CounterLimit>());
}
#define GetLCount(TagID) \
LHighest<__COUNTER__, void>(TagID())
#define LIncrementTag_(TagID) \
typename std::conditional< \
GetCount(ExtraCounter<TagID, GetCount(ExtraCounter<TagID, CounterLimit>)>) == CounterLimit - 1, \
ExtraCounter<TagID, CounterLimit>, \
ExtraCounter<TagID, GetCount(ExtraCounter<TagID, CounterLimit>)>>::type
#define IncrementLCount(TagID) \
template <unsigned int GetID, typename = typename std::enable_if<(GetID > __COUNTER__ + 7)>::type> \
constexpr unsigned int Highest( \
LIncrementTag_(TagID), \
TemplateInt<GetCount(LIncrementTag_(TagID)) + 1> Value) \
{ \
return decltype(Value)::Value; \
}
Testing
测试
struct Counter3 {};
constexpr unsigned int Read7 = GetLCount(Counter3);
IncrementLCount(Counter3);
constexpr unsigned int Read8 = GetLCount(Counter3);
回答by Aleksey F.
Unfortunately, template metaprogramming is essentially a functional language, and as such lacks global variables or modifiable state which would implement such a counter.
Or is it?
不幸的是,模板元编程本质上是一种函数式语言,因此缺乏实现这种计数器的全局变量或可修改状态。
或者是吗?
C++ allows compile time counters (i.e. without __COUNTER__
, __LINE__
or other approaches proposed here earlier) as well as allocating and defining inner int unique ID for each template instance. See v1solution for the counter implemented with template metaprograming using chaining allocated IDs and v2for the second use case. Both solutions are answers for "How can I generate dense unique type IDs at compile time?". But the task has an important requirement about the only ID allocator.
C++ 允许编译时间计数器(即没有__COUNTER__
,__LINE__
或前面提出的其他方法)以及为每个模板实例分配和定义内部 int 唯一 ID。请参阅v1解决方案,了解使用链接分配的 ID 和v2 的模板元编程实现的计数器,用于第二个用例。这两种解决方案都是“如何在编译时生成密集的唯一类型 ID?”的答案。. 但是该任务对唯一的 ID 分配器有一个重要的要求。
回答by Fabio A.
I've gone through this whole thing myself and eventually have come up with a solution that seems to be standard-compliant (at the time I'm writing this) and works with gcc, clang, msvc and icc, in all their recent versions and in most of the old ones.
我自己经历了这整件事,最终想出了一个似乎符合标准的解决方案(在我写这篇文章的时候),并且在 gcc、clang、msvc 和 icc 的所有最新版本中都可以使用并且在大多数旧的。
I've talked about the whole process into another post on here: C++ compile time counters, revisited.
我已经在这里的另一篇文章中讨论了整个过程:C++ compile time counters, revisited。
I've then packaged the solutioninto a fameta::counter
class that solves a few remaining quirks.
然后,我将解决方案打包到一个fameta::counter
类中,以解决一些剩余的怪癖。
You can find it on github.
你可以在 github 上找到它。