Pot*_*ter 60 c++ templates state metaprogramming
出于内省的目的,有时我想自动为类型或类似的东西分配序列号.
不幸的是,模板元编程本质上是一种功能语言,因此缺乏实现这种计数器的全局变量或可修改状态.
或者是吗?
按请求的示例代码:
#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"
}
Pot*_*ter 45
嗯...是的,模板元编程缺乏预期的副作用.我被旧版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 ) > )
快速演示(见它运行):
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 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
声明应放在命名空间内,宏中使用的所有名称除外counter_crumb应完全限定.该counter_crumb模板通过与ADL一起被发现constant_index的类型.
所述COUNTER_LINK_NAMESPACE宏可用于递增在多个名称空间的范围一个计数器.
Jos*_*ews 21
我相信MSVC和GCC都支持一个__COUNTER__预处理器令牌,它在其位置上取代了单调递增的值.
iam*_*ind 18
我想在很长一段时间内解决这个问题,并提出了一个非常简洁的解决方案.至少我应该有一个upvote试试这个.:))
以下库代码实现了命名空间级功能.即我成功实施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}; }
该技术使用模板元编程并利用__LINE__宏.见结果从你的答案代码.
303*_*303 13
使用 C++20,可以在未计算的上下文中使用 lambda,这允许在利用臭名昭著的友元注入技术的同时提供相当优雅的解决方案。可以在此处找到实时示例,并使用 Clang 17.0.1、GCC 13.2 和 MSVC 19.38 进行测试。
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{}, typename = decltype([]{})>
consteval auto unique_id() {
    if constexpr (not counter<Id>::exists(Id)) return Id;
    else return unique_id<Id + 1>();
}
static_assert(unique_id() == 0);
static_assert(unique_id() == 1);
static_assert(unique_id() == 2);
static_assert(unique_id() == 3);
默认模板参数的 lambda 是必要的,以确保编译器为模板化函数的每个隐式实例化重新评估编译时条件语句。即使使用默认带有条件语句结果的非类型模板参数,编译器似乎也不会完全重新评估它,而是缓存结果。
由于共享是关心,我花了几个小时摆弄基础示例,这方面提供我也将发布我的解决方案.
文章中链接的版本有两个主要缺点.由于最大递归深度(通常约为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.请参阅注释.)
// 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;
}
这是另一种替代实现。 /sf/answers/432198441/可能更好,但即使在纸上手动完成几个增量之后,我仍然不太理解数学/过滤。
这使用 constexpr 函数递归来计算非模板声明函数的数量Highest。  __COUNTER__用作生成机制,以防止新的声明Highest进行自递归。
这只能在 clang 上为我编译(3.3)。我不确定它是否合规,但我充满希望。g++ 4.8 由于某些未实现的功能而失败(根据错误)。由于 constexpr 错误,英特尔编译器 13 也失败。
每个计数器的最大计数为 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
如果您想要高于 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);