C + + 支持编译时计数器吗?

出于自省的目的,有时我想自动分配序列号的类型,或类似的东西。

不幸的是,模板超编程本质上是一种函数式语言,因此缺乏全局变量或可修改的状态来实现这样的计数器。

真的吗?


按请求编写的示例代码:

#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"
}
32092 次浏览

我相信 MSVC 和 GCC 都支持 __COUNTER__预处理令牌,取而代之的是单调递增的值。

您可以使用 Boost. 预处理器中的 BOOST_PP_COUNTER

优点: 它甚至可以用于宏

缺点: 对于整个程序只有一种“计数器类型”,但是对于专用计数器可能会重新实现该机制

嗯... 是的,模板超编程没有预期的副作用。我被 GCC 的旧版本中的一个 bug 和标准中一些不明确的措辞所误导,相信所有这些特性都是可能的。

但是,至少可以在很少使用模板的情况下实现名称空间范围的功能。函数查找可以从声明的函数集中提取数值状态,如下所示。

图书馆代码:

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

快速演示(看着它跑) :

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更新

下面是使用 C + + 11 constexpr代替 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

Http://ideone.com/yp19oo

声明应该放在一个名称空间中,除了 counter_crumb之外,宏中使用的所有名称都应该是完全限定的。通过与 constant_index类型的 ADL 关联可以找到 counter_crumb模板。

可以使用 COUNTER_LINK_NAMESPACE宏在多个命名空间的范围内递增一个计数器。

我想解决这个问题相当长的一段时间,并提出了一个非常短的干净的解决方案。至少我应该得到一个赞成票来试试这个。:))

下面的库代码实现了命名空间级别的功能。也就是说,我成功地实现了 counter_readcounter_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}; }

此技术使用 模板元编程模板元编程并利用 __LINE__宏。 有关答案中的代码,请参见 结果

这是另一个替代实现。https://stackoverflow.com/a/6174263/1190123可能更好一些,但即使在手动处理了几个增量之后,我仍然不太理解数学/过滤。

这使用 Constexpr 函数递归来计算非模板声明的 Highest函数的数量。__COUNTER__用作一种生成机制,用于防止 Highest的新声明执行自递归。

这只是在 clang 上为我编译(3.3)。我不确定它是否符合要求,但我希望如此。由于一些未实现的特性(根据错误) ,g + + 4.8失败。英特尔编译器13也失败,由于一个 Constexpr 错误。

256水平计数器

每个计数器的最大计数为250(CounterLimit)。CounterLimit 可以增加到256,除非您实现下面的 LCount。

实施

#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; \
}

测试

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

输出

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水平计数器

如果希望值高于256,我认为可以合并计数器。我做了250 * 250(尽管我没有真正测试过2)。对于编译器编译时递归限制,CounterLimit 必须降低到250左右。需要注意的是,为我编译这个代码要花费更多的时间。

实施

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

测试

struct Counter3 {};
constexpr unsigned int Read7 = GetLCount(Counter3);
IncrementLCount(Counter3);
constexpr unsigned int Read8 = GetLCount(Counter3);

不幸的是,模板超编程本质上是一种功能性的 语言,因此缺乏全局变量或可修改的状态 会采取这样的反制措施。

是吗?

C + + 允许编译时计数器(即不使用 __COUNTER____LINE__或前面提出的其他方法) ,以及为每个模板实例分配和定义内部 int 唯一 ID。有关使用模板元编程实现的计数器,请参见 V1解决方案,第二个用例使用链接分配的 ID 和 V2。这两种解决方案都是 “如何在编译时生成密集的唯一类型 ID?”的答案。但是这个任务对唯一的 ID 分配器有一个重要的要求。

因为分享是关心,我花了几个小时摆弄基本的例子 这个方提供我要张贴我的解决方案以及。

本文链接到的版本有两个主要缺点。由于最大递归深度(通常在256左右) ,它可以计数的最大值也非常低。而且一旦数量超过几百,编译所需的时间是巨大的。

通过实现二进制搜索来检测计数器的标志是否已经设置,可以大大增加最大计数(通过 MAX _ DEPTH 可控) ,同时还可以提高编译时间。=)

用法例子:

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

完全工作的代码,末尾有示例: (除了当当声。请参阅注释。)

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

我自己经历了整个过程,最终找到了一个看起来符合标准的解决方案(在我写这篇文章的时候) ,可以在 gcc、 clang、 msvc 和 icc 的所有最新版本和大多数旧版本中使用。

我已经在这里的另一篇文章中谈到了整个过程: C + + 编译时间计数器,重新访问

然后,我将 解决方案打包成一个 fameta::counter类,它解决了一些遗留的问题。

你可以 在 Github 上找到的

随着 C + + 20的发展。

你有 Source _ location,它可以从 C + + 函数中生成索引,而不需要宏。

样本代码

#include <source_location> // merged in C++20


constexpr auto Generate(const std::source_location& location =
std::source_location::current()) {
return location.line();
}

现在您可以通过一个源文件使用它作为计数器,或者为具有文件名的源位置添加编译时散列函数以获得唯一索引。

从 C + + 17开始,这个简单的解决方案对我来说非常好用。 而且开销是最小的。看起来几乎像编译时计数器。

struct counter
{
private:
static inline uint32 count = 0;


public:
static inline auto next() { return ++count; }
};

使用 C + + 20,可以在未评估的上下文中使用 lambdas,这使得在利用臭名昭著的 朋友注射技术朋友注射技术的同时提供了一个相当优雅的解决方案。现场示例可以找到 给你,使用 Clang 15、 GCC 12.2和 MSVC 19.33进行测试。

template<auto Id>
struct counter {
using tag = counter;


struct generator {
friend consteval auto is_defined(tag)
{ return true; }
};
friend consteval auto is_defined(tag);


template<typename Tag = tag, auto = is_defined(Tag{})>
static consteval auto exists(auto)
{ return true; }


static consteval auto exists(...)
{ return generator(), false; }
};


template<auto Id = int{}, auto = []{}>
consteval auto unique_id() {
if constexpr (counter<Id>::exists(Id)) {
return unique_id<Id + 1>();
} else {
return Id;
}
}


static_assert(unique_id() == 0);
static_assert(unique_id() == 1);
static_assert(unique_id() == 2);
static_assert(unique_id() == 3);

默认模板参数的 lambda 是必要的,以确保编译器将重新计算模板函数的每个隐式实例化的编译时间 If判断语句。即使在使用默认的非类型模板参数时,编译器似乎也不会完全重新计算 If判断语句,而是缓存结果。