C++ 是不是支持编译时计数器?

Posted

技术标签:

【中文标题】C++ 是不是支持编译时计数器?【英文标题】:Does C++ support compile-time counters?C++ 是否支持编译时计数器? 【发布时间】:2011-09-04 05:05:27 【问题描述】:

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

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

是吗?


请求示例代码:

#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"

【问题讨论】:

你能举一个简短的例子来演示确切的问题是什么吗? 不能使用X&lt;__LINE__&gt; 吗?这将始终在给定文件中提供唯一编号(可能不是序列号)。 @iammilind:这不适用于多个标头,并且在不需要需要唯一性时不会重复返回相同的结果。模板解决方案更强大。查看答案。 相关:C++ construct that behaves like the COUNTER macro. 【参考方案1】:

嗯……是的,模板元编程没有预期的副作用。我被旧版本 GCC 中的一个错误和标准中的一些不清楚的措辞误导了我相信所有这些功能都是可能的。

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

库代码:

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

快速演示 (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 更新

这是使用 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 除外)都应完全限定。 counter_crumb 模板是通过与 constant_index 类型的 ADL 关联找到的。

COUNTER_LINK_NAMESPACE 宏可用于在多个命名空间范围内增加一个计数器。

【讨论】:

链接到您的第一个在线运行的代码似乎无效。 @GingerPlusPlus 谢谢,我会通知 IDEone。无论如何,结果与第二个代码相同。 cn&lt;N&gt; 可以由编译器自行决定填充。所以sizeof( cn&lt;N&gt; )可以是任何值>= N。需要使用sizeof( cn&lt;N&gt;::data ) 另外值得注意的是(1)这样的方法在单独编译时注定会失败,并且(2)它们有点危险。将 id 用于外部存储,例如序列化,因为 id 可以取决于标头包含顺序。 @Louis-JacobLebel 已经有一段时间了,但重新阅读这段代码,我只是想将constant_indexcounter_crumb 封装在一个私有命名空间中。它只是一个简单的库,但带有预处理器宏接口。 (我真的应该创建一个带有包含这个 sn-p 的标头的 Git 存储库。)【参考方案2】:

我相信 MSVC 和 GCC 都支持 __COUNTER__ 预处理器令牌,该令牌具有单调递增的值。

【讨论】:

如果我的前缀正确,你应该检查导致像duodecilliotonically 这样的词的美感......:P 这是最常见的解决方案,但 1. 不标准; 2. 不可重复使用——每个翻译单元只有一个计数器; 3.不修改就无法读取。【参考方案3】:

我想解决这个问题已经有一段时间了,并想出了一个非常简单的解决方案。至少我值得一票来试试这个。 :))

以下库代码实现命名空间级别的功能。即我成功实施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__ 宏。 请参阅 the result 以获取答案中的代码。

【讨论】:

非常好!但是,这会导致每个源代码行都有一定程度的模板嵌套,因此对于大文件可能无法编译。 另外,如果使用两个不同的头文件会混淆。 (但命名空间可用于包含损坏。) 1 ideone.com/dOXTG。正如您从错误消息中看到的那样,512 正是保证与此编译器版本一起工作的最高值。 @Potatoswatter,我再次偶然发现了这个问题,只是想知道上下文是什么。上次你说限制只有 512,但是当我检查 G++ 时,它也适用于更大的数字。见demo。可能是我错过了什么。如果你不介意,你能指出这个解决方案的问题吗? @iammilind 它实例化 O(N) 个模板,其中 N 是源文件的长度。这是次优的,尽管它可能有效。在任何给定平台上,最大模板深度都会随着时间的推移而增加。【参考方案4】:

由于分享是关怀,我花了几个小时摆弄基本示例 this 边提供,我也将发布我的解决方案。

文章中链接的版本有两个主要缺点。由于最大递归深度(通常在 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;

完整的代码,最后带有示例:(clang 除外。请参阅 cmets。)

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

【讨论】:

你是对的。我刚刚用 vc++、gcc 和 clang 对其进行了测试。前两个工作,但铿锵没有。原因是,用于检查 adl_flag 是否已定义的表达式不适用于 clang。 (这个:class = char[noexcept( adl_flag(flag&lt;N&gt;()) ) ? +1 : -1])如果你能找到一个正确返回类型的,只有adl_flag(flag&lt;N&gt;)已经被定义了,这个才有效。 尝试在底部查找here 以进行clang 修复。将其合并到代码中可能需要更多的工作,但应该是可行的。 只回答不使用宏的答案 读者注意:CWG 已表示希望消除允许此操作的朋友漏洞。它可能不是面向未来的(并且并不总是适用于所有编译器)。更多信息请看这里:b.atch.se/posts/constexpr-meta-container/#conclusion-wg21 也不适用于 gcc。 coliru.stacked-crooked.com/a/e7603c4b9e134175【参考方案5】:

您可以使用来自 Boost.Preprocessor 的BOOST_PP_COUNTER

优点:它甚至适用于宏

缺点:整个程序只有一种“计数器种类”,但可以为专用计数器重新实现机制

【讨论】:

不幸的是,与 COUNTER 一样,此计数器缺少在翻译单元全面的全球上下文中使用的相同支持问题..【参考方案6】:

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

这使用 constexpr 函数递归来计算非模板声明的 Highest 函数的数量。 __COUNTER__ 用作生成机制,以防止 Highest 的新声明进行自递归。

这仅在我 (3.3) 上编译。我不确定它是否合规,但我充满希望。 g++ 4.8 由于某些未实现的功能而失败(根据错误)。由于 constexpr 错误,英特尔编译器 13 也失败了。

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

【讨论】:

请注意,该限制适用于可以评估计数器的次数,而不是其最大值。对不起,我可能应该解释我使用的数学。总的来说,我的实现是如何工作的……它相当复杂。但是我的读写是 O(log limit value),而这似乎是 O(limit accesses)。 请注意,您可以使用__VA_ARGS__ 和可变参数宏将, 作为宏参数传递,避免COMMA 感谢__VA_ARGS__ 提示!我并不是要批评你的回答;即使你解释了,我也不确定我是否具备必要的智力。不过,如果您确实添加了更多解释,我会仔细阅读。 至于复杂性,我认为它是 O(极限值)......如果我正确理解我的代码(哈哈)它会在 GetLCount 中的 GetCount3 * CounterLimit 中进行 CounterLimit 递归. __COUNTER__ 只应该改变函数可见性和强制模板重新实例化。我只是检查了一下,CounterLimit 可以是 250 没有问题,所以我认为我最初误判了递归。 我尝试了一个 IncrementLCount 32000 次的文件,但在大约 20 分钟(4GB RAM,+2GB 交换)后,clang 被内核杀死(内存不足)。【参考方案7】:

我自己经历了这整件事,最终提出了一个似乎符合标准的解决方案(在我写这篇文章的时候)并且可以与 gcc、clang、msvc 和 icc 一起使用他们的最新版本和大多数旧版本。

我已经在另一个帖子中谈到了整个过程:C++ compile time counters, revisited。

然后我将the solution 打包到fameta::counter 类中,该类解决了一些剩余的怪癖。

你可以find it on github。

【讨论】:

【参考方案8】:

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

是吗?

C++ 允许编译时间计数器(即没有__COUNTER____LINE__ 或此处之前提出的其他方法)以及为每个模板实例分配和定义内部 int 唯一 ID。请参阅v1 解决方案,了解使用链接分配的 ID 的模板元编程实现的计数器,以及第二个用例的v2。这两种解决方案都是"How can I generate dense unique type IDs at compile time?" 的答案。但该任务对唯一的 ID 分配器有一个重要要求。

【讨论】:

【参考方案9】:

从 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++20 之前可能使用非标准 __builtin_LINE() 作为默认参数。

以上是关于C++ 是不是支持编译时计数器?的主要内容,如果未能解决你的问题,请参考以下文章

英特尔自动矢量化行程计数解释?

C++:在优化结束时计数零

C++ String的引用计数写时复制 的实现 《More Effective C++》

C++ String的引用计数写时复制 的实现 《More Effective C++》

C++中使用构造函数和析构函数的对象计数器

如何在 C++ 中为共享计数器实现简单的比较和交换