Has anyone ever had a use for the __COUNTER__ pre-processor macro?
Asked Answered
T

20

69

The __COUNTER__ symbol is provided by VC++ and GCC, and gives an increasing non-negative integral value each time it is used.

I'm interested to learn whether anyone's ever used it, and whether it's something that would be worth standardising?

Tailband answered 17/3, 2009 at 2:14 Comment(4)
Curious: is COUNTER defined in the C99 standard? (Or maybe the C++ standard?)Neonate
I don't think it's defined in either. AFAIK, it's an extension that started in VC++, and went over to GCC recently. Don't know if any other compilers support. Maybe if there's a reallu important use for it there'd be reason to standardize ...Tailband
I've used it, but it was so long ago that I can't remember why.Possibly
@dcw: clang supports it.Tauromachy
G
13

It's used in the xCover code coverage library, to mark the lines that execution passes through, to find ones that are not covered.

Gear answered 9/5, 2009 at 23:9 Comment(0)
P
67

__COUNTER__ is useful anywhere you need a unique name. I have used it extensively for RAII style locks and stacks. Consider:

struct TLock
{
  void Lock();
  void Unlock();
}
g_Lock1, g_Lock2;

struct TLockUse
{
  TLockUse( TLock &lock ):m_Lock(lock){ m_Lock.Lock(); }
  ~TLockUse(){ m_Lock.Unlock(); }

  TLock &m_Lock;
};

void DoSomething()
{
  TLockUse lock_use1( g_Lock1 );
  TLockUse lock_use2( g_Lock2 );
  // ...
}

It gets tedious to name the lock uses, and can even become a source of errors if they're not all declared at the top of a block. How do you know if you're on lock_use4 or lock_use11? It's also needless pollution of the namespace - I never need to refer to the lock use objects by name. So I use __COUNTER__:

#define CONCAT_IMPL( x, y ) x##y
#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y )
#define USE_LOCK( lock ) TLockUse MACRO_CONCAT( LockUse, __COUNTER__ )( lock )

void DoSomething2()
{
  USE_LOCK( g_Lock1 );
  USE_LOCK( g_Lock2 );
  // ...
}

But don't get hung up on the fact I called the objects locks - any function(s) that need to get called in matching pairs fit this pattern. You might even have multiple uses on the same "lock" in a given block.

Part answered 18/8, 2009 at 17:25 Comment(3)
It's a significantly better idea to use a single use_lock(g_Lock1, g_Lock2) function that is smart enough to order the locks to avoid deadlocking.Darkling
Why are MACRO_CONCAT and CONCAT_IMPL needed? Can't we directly write LockUse##__COUNTER__?Funky
@Cœur - FYI, I asked that same question a couple of years ago on this site: Why is a level of indirection needed for this concatenation macro?Salvidor
H
14

I've used it in a compile-time assertion macro to have the macro create a name for a typedef that will be unique. See

if you want the gory details.

Hyperform answered 17/3, 2009 at 4:52 Comment(5)
Why do you want unique name for each of typedefs? They will be either defined eqiuvalently or compilation will fail.Custumal
@nOrd - hmm - either it's over-engineered or there's a reason that's escaping me... but, I probably won't revisit it, since the C_ASSERT stuff seems to be working just fine for me.Hyperform
Equivalent definitions are an error at least in C89, I believe. I think C99 and C++11 both allow redundant typedefs, but they weren't always permitted.Siqueiros
If you invoke the macro more than once, you need each typedef to be unique. If __COUNTER__ is not available, the failover is to use __LINE__, but that will fail of you have the bad luck of using the macro on the same line in two different source files.Ademption
How do you refer to that typedef again? __COUNTER__ will change each time you use it. A unique name that you cannot mention again has limited uses.Antarctic
C
13

I've never used it for anything but a DEBUG macro. It's convenient to be able to say

#define WAYPOINT \
    do { if(dbg) printf("At marker: %d\n", __COUNTER__); } while(0);
Caird answered 17/3, 2009 at 2:19 Comment(22)
Interesting. But how is this as good as / better than using FILE. After all, that will tell you exactly where the waypoint is, rather than perhaps having to search the file for WAYPOINT instances? Am I missing something ;-)Tailband
Well, you might be missing me saying "this is all I've ever found it useful for." Which isn't very.Caird
Ok. Thanks for clarifying. btw, you might want to look at the code. Not sure the format specifier type is what you intended. ;-)Tailband
Me again: kudos on the do {} while(0), btw. :-)Tailband
What, %d? It was always %d. Nothing else. Never changed. Yeah, that's the ticket.Caird
I think using && may be more useful than the do/if. That reduces readability, though...Neonate
strager, what benefit do you see in using &&? And can you construct a case where WAYPOINT could appear that would give unexpected results using && instead?Caird
@Martin, Using && allows the 'function' to be used as a normal function with a return value (in most cases). You can for example do: if(WAYPOINT) { more debug }. Macro would be: #define WAYPOINT (dbg && (printf("..", _COUNTER) >= 0))Neonate
Right. Now think about this inside an if. See, eg, here: mail.nl.linux.org/kernelnewbies/2001-09/msg00230.html There's a reason this is the canonical way of doing a complex #defineCaird
@Martin, I'm not convinced do/white is as effective as && in this case. Whatever, though. I'll stop my arguing.Neonate
No there isn't. It's there on purpose. If someone writes WAYPOINT; the extra ; is a no-op, silently optimized out. Do it the other way and it can become a syntax error somewhere south of the macro, with the cause hidden in the macro body.Caird
@CharlieMartin: Put parenthesis after the name and developers stop making that mistake: WAYPOINT(); Also, the "silent" semicolon produces warnings that irritate devs.Darkling
Re the two ;'s -- not on any compiler I've got access to.Caird
The second semicolon will make it fail for something like if (foo) WAYPOINT; else bar();. You're re-introducing the problem that do { foo } while (0) solves :)Jocasta
"Doctor, it hurts when I do this." "Well, don't do that!" -- Braces are your friend.Caird
@CharlieMartin The whole point of do { foo } while (0) in macros is to avoid that problem with if/else statements. If you're not worried about if statements without braces (which is fair enough), why not remove the whole while (0) wrapper? Either way, the while (0) wrapper is rendered totally pointless with that trailing semicolon.Profuse
@ArthurTacca You can't know where your macro will be used in the future, so you can't know you're not worried about braceless ifs.Caird
In that case you DO need to remove the trailing semicolon! That's what I meant by "either way ...".Profuse
So, you get unique waypoint messages. That's fine. But how do you find the line of code that actually produced that message?!? I'd much rather insert an fprintf(stderr, "Checkpoint 1\n"); and rely on an editor macro to adjust the number whenever I paste it into the code. That way I can clearly make the connection by looking at the code and its output.Misstate
@cmaster try FILE and LINECaird
@CharlieMartin If you use __FILE__ and __LINE__ to output the precise location, the use of __COUNTER__ to make the messages distinguishable is pretty mute...Misstate
You mean "moot" and so what? I answered the question.Caird
G
13

It's used in the xCover code coverage library, to mark the lines that execution passes through, to find ones that are not covered.

Gear answered 9/5, 2009 at 23:9 Comment(0)
F
12

I'm interested to learn whether anyone's ever used it,

Yes, but as you can see from many examples in this Q&A, __LINE__, which is standardized, would also be sufficient in most cases.

__COUNTER__ is only really necessary in cases where the count must increase by one each time, or it must have continuity over several #include files.

and whether it's something that would be worth standardising?

__COUNTER__, unlike __LINE__, is very dangerous because it depends on which header files are included and what order. If two .cpp files (translation units) include a header file that use __COUNTER__, but the header file obtains different count sequences in the different instances, they may use different definitions of the same thing and violate the one-definition rule.

One-definition rule violations are very difficult to catch and potentially create bugs and security risks. The few use-cases of __COUNTER__ don't really outweigh the downside and lack of scalability.

Even if you never ship code that uses __COUNTER__, it can be useful when prototyping an enumeration sequence, saving you the trouble of assigning names before the membership is concrete.

Francois answered 25/1, 2015 at 13:16 Comment(0)
D
3

If I'm understanding the functionality correctly, I wished I had that functionality when I was working in Perl, adding an Event Logging function into an existing GUI. I wanted to ensure that the needed hand testing (sigh) gave us complete coverage, so I logged every test point to a file, and logging a __counter__ value made it easy to see what was missing in the coverage. As it was, I hand coded the equivalent.

Dewberry answered 17/3, 2009 at 5:16 Comment(1)
As I reread my own post, I didn't really hand-code the equivalent. I just put counters in by hand. (hard coded all the numbers throughout each file.) That's why I wish I'd had the functionality.Dewberry
P
3

It is used by Boost.Asio to implement stackless coroutines.

See this header file and examples.

Resulting coroutines look like this:

struct task : coroutine
{
  ...
  void operator()()
  {
    reenter (this)
    {
      while (... not finished ...)
      {
         ... do something ...
         yield;
         ... do some more ...
         yield;
       }
     }
   }
   ...
};
Plough answered 15/7, 2016 at 23:7 Comment(0)
D
3

A usage is in TensorFlow's REGISTER_KERNEL_BUILDER macro. Each TensorFlow Op could have one or more kernels as its implementations. These kernels are registered with a registrar. The registration of a kernel is done by defining a global variable -- the constructor of the variable can do the registration. Here the authors use __COUNTER__ to give each global variable a unique name.

#define REGISTER_KERNEL_BUILDER(kernel_builder, ...) \
  REGISTER_KERNEL_BUILDER_UNIQ_HELPER(__COUNTER__, kernel_builder, __VA_ARGS__)

#define REGISTER_KERNEL_BUILDER_UNIQ_HELPER(ctr, kernel_builder, ...) \
  REGISTER_KERNEL_BUILDER_UNIQ(ctr, kernel_builder, __VA_ARGS__)

#define REGISTER_KERNEL_BUILDER_UNIQ(ctr, kernel_builder, ...)          \
  static ::tensorflow::kernel_factory::OpKernelRegistrar                \
  registrar__body__##ctr##__object(                                 \
      SHOULD_REGISTER_OP_KERNEL(#__VA_ARGS__)                       \
      ? ::tensorflow::register_kernel::kernel_builder.Build()   \
      : nullptr,                                                \
      #__VA_ARGS__, [](::tensorflow::OpKernelConstruction* context) \
            -> ::tensorflow::OpKernel* {                \
              return new __VA_ARGS__(context);          \
            });
Defeasance answered 11/6, 2017 at 4:23 Comment(0)
I
3

Generating Class Type IDs (C++)

I've used __COUNTER__ to automatically generate type IDs for Entities & Colliders in an object-oriented game.

This game uses polymorphism to achieve its functionality. To serialize child objects, I had to figure out a way to store Entity child types & serialize/deserialize them for scene saving & loading. When reading an entity from a save file (deserializing), I needed to know what properties to expect to read; with __COUNTER__, I have a unique and constant ID for each entity class and can load them in as the proper entity type using this ID.

This approach means that to make a new Entity type serializable, all I have to add is typeID = __COUNTER__; within the constructor to overwrite the default ID. In the case of Sprite:

Sprite(/* TODO: Sprite Arguments */) : Entity(/* TODO: Entity Arguments */) {
    typeID = __COUNTER__;
}

... and go on to outline its iostream overloads:

friend std::ostream& operator<<(std::ostream& os, const Sprite& rhs) {
    return os << /* TODO: Outline Output */;
}
friend std::istream& operator>>(std::istream& is, Sprite& rhs) {
    return is >> /* TODO: Outline Input */;
}

It's a very lightweight approach to generating type IDs for your classes, and avoids a bunch of complicated logic. As a preprocessor command it's pretty basic, but it provides a useful tool for some key appliances.

Note: If you want to restart the ID value to 0 when calling the counter, store its value on the generation of your first ID and subtract all subsequent IDs by that value.

Thanks for reading! -YZM

Internment answered 12/4, 2021 at 3:18 Comment(0)
T
2

I've used it for a driver shim layer, where I needed to make sure at least one physical driver was enabled.

For example:

#if defined( USE_DRIVER1 )
#include "driver1.h"
int xxx1 = __COUNTER__;
#endif
#if defined( USE_DRIVER2 )
#include "driver2.h"
int xxx2 = __COUNTER__;
#endif
#if __COUNTER__ < 1
#error Must enable at least one driver.
#endif
Tripartition answered 19/3, 2020 at 1:30 Comment(0)
Q
2

In this blog post it is used for simulating the defer statement of golang in C++11.

template <typename F>
struct privDefer {
    F f;
    privDefer(F f) : f(f) {}
    ~privDefer() { f(); }
};

template <typename F>
privDefer<F> defer_func(F f) {
    return privDefer<F>(f);
}

#define DEFER_1(x, y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_3(x)    DEFER_2(x, __COUNTER__)
#define defer(code)   auto DEFER_3(_defer_) = defer_func([&](){code;})

Then you can do:

int main()
{
    FILE* file = open("file.txt");
    defer(fclose(file));

    // use the file here
    // ....
}
Quadrille answered 25/4, 2020 at 8:59 Comment(4)
This is much better handled using RAII idioms. I know the blog you reference says it is inconvenient writing a throwaway class, but std::unique_ptr solve that problem using a custom deleter.August
@MattEding Well, this is already using RAII idiom, in some way. The blog doesn't say that is incovenient.Quadrille
True, it uses RAII in the implementation, but the manual call afterward seems to defeat the spirit of it. The blog implies inconvenience with: "You may say that RAII in C++ solves this issue but this would require a class wrapper. defer allows for the same thing as RAII but without manually creating a wrapper type."August
@MattEding I don't see that implication in the sentence, maybe you just see the inconvenience by yourself :)Quadrille
N
1

It's used in ClickHouse's metrics system.

namespace CurrentMetrics
{
    #define M(NAME) extern const Metric NAME = __COUNTER__;
        APPLY_FOR_METRICS(M)
    #undef M
    constexpr Metric END = __COUNTER__;

    std::atomic<Value> values[END] {};    /// Global variable, initialized by zeros.

    const char * getDescription(Metric event)
    {
        static const char * descriptions[] =
        {
        #define M(NAME) #NAME,
            APPLY_FOR_METRICS(M)
        #undef M
        };

        return descriptions[event];
    }

    Metric end() { return END; }
}
Nehru answered 13/12, 2017 at 6:40 Comment(0)
A
1

I have found it useful for displaying steps in a UI. This makes it really easy to add, remove, or reorder steps without worrying about the steps getting mislabeled.

#include <iostream>

#define STR_IMPL(s)  #s
#define STR(s)  STR_IMPL(s)
#define STEP  STR(__COUNTER__) ": "

int main()
{
    std::cout 
        << STEP "foo\n"
        << STEP "bar\n"
        << STEP "qux\n"
        ;
}

Output:

0: foo
1: bar
2: qux

Having it start from 1 instead of 0 is left as an exercise.

August answered 14/7, 2021 at 16:56 Comment(0)
A
1

__COUNTER__ can be used to establish unique local variables. The problem with __COUNTER__ is that its value is different on each expansion. But what we can do is split our macro into two:

#define MACRO_IMPL(COUNTER, ARG1, ARG2, ..., ARGN)

#define MACRO(ARG1, ARG2, ..., ARGN) MACRO_IMPL(__COUNTER__, ARG1, ARG2, ... ARGN)

So now MACRO_IMPL has a unique counter, via the COUNTER argument value, which it can use to generate local symbols that are defined and referenced multiple times. E.g.

#define CAT(A, B) A ## B
#define XCAT(A, B) CAT(A, B)
#define U(COUNTER) XCAT(__U, COUNTER)

#define REPEAT_IMPL(C, N) for (int U(C) = 0; U(C) < (N); U(C)++)

#define REPEAT(N) REPEAT_IMPL(__COUNTER__, N)

REPEAT (42) { puts("Hey!"); REPEAT (73) { puts("Cool!"); } }

Expansion by gcc -E -:

# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "<stdin>"
# 9 "<stdin>"
    for (int __U0 = 0; __U0 < (42); __U0++) { puts("Hey!"); for (int __U1 = 0; __U1 < (73); __U1++) { puts("Cool!"); } }

I put the loops in one line on purpose; that's a situation where using __LINE__ instead of __COUNTER__ could break.

Antarctic answered 14/7, 2022 at 17:51 Comment(0)
F
0

__COUNTER__ is guaranteed to be unique unlike __LINE__. Some compilers allow __LINE__ to be reset. #include files will also reset __LINE__.

Flypaper answered 13/10, 2014 at 12:49 Comment(2)
All standard C and C++ compilers are required to provide the feature that resets __LINE__.Francois
But __LINE__ is useful in that it is not unique when a particular macro is being expanded: all occurrences of __LINE__ are the same, which lets you do useful things like #define loop(...) ... for (__uniq(i) = 0; __uniq(i) < N; __uniq(i)++) where __uniq uses __LINE__ to generate an identifier like __i_42. All occurrences of this will share the line number wheree the macro call occurs.Antarctic
L
0

In our code we forgot to add testcases for some of our products. I implemented now some macros so we can assert at compile time that we have testcases for each product that we are adding or removing.

Luthern answered 10/10, 2018 at 15:36 Comment(0)
L
0

__COUNTER__ is very useful when you are encrypting strings in runtime and you want every string to have a unique key, without storing a counter somewhere for the key of your encryption you can use Counter to be sure that every string has it's own unique key!.

I use it in my XorString 1 header library which decrypts strings in run-time, so if any hackers/crackers try to look at my binary file they won't find the strings there, but when the program runs every string is decrypted and shown as normal.

#pragma once
#include <string>
#include <array>
#include <cstdarg>

#define BEGIN_NAMESPACE( x ) namespace x {
#define END_NAMESPACE }

BEGIN_NAMESPACE(XorCompileTime)

constexpr auto time = __TIME__;
constexpr auto seed = static_cast< int >(time[7]) + static_cast< int >(time[6]) * 10 + static_cast< int >(time[4]) * 60 + static_cast< int >(time[3]) * 600 + static_cast< int >(time[1]) * 3600 + static_cast< int >(time[0]) * 36000;

// 1988, Stephen Park and Keith Miller
// "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"
// Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:
// with 32-bit math and without division

template < int N >
struct RandomGenerator
{
private:
    static constexpr unsigned a = 16807; // 7^5
    static constexpr unsigned m = 2147483647; // 2^31 - 1

    static constexpr unsigned s = RandomGenerator< N - 1 >::value;
    static constexpr unsigned lo = a * (s & 0xFFFF); // Multiply lower 16 bits by 16807
    static constexpr unsigned hi = a * (s >> 16); // Multiply higher 16 bits by 16807
    static constexpr unsigned lo2 = lo + ((hi & 0x7FFF) << 16); // Combine lower 15 bits of hi with lo's upper bits
    static constexpr unsigned hi2 = hi >> 15; // Discard lower 15 bits of hi
    static constexpr unsigned lo3 = lo2 + hi;

public:
    static constexpr unsigned max = m;
    static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;
};

template <>
struct RandomGenerator< 0 >
{
    static constexpr unsigned value = seed;
};

template < int N, int M >
struct RandomInt
{
    static constexpr auto value = RandomGenerator< N + 1 >::value % M;
};

template < int N >
struct RandomChar
{
    static const char value = static_cast< char >(1 + RandomInt< N, 0x7F - 1 >::value);
};

template < size_t N, int K, typename Char >
struct XorString
{
private:
    const char _key;
    std::array< Char, N + 1 > _encrypted;

    constexpr Char enc(Char c) const
    {
        return c ^ _key;
    }

    Char dec(Char c) const
    {
        return c ^ _key;
    }

public:
    template < size_t... Is >
    constexpr __forceinline XorString(const Char* str, std::index_sequence< Is... >) : _key(RandomChar< K >::value), _encrypted{ enc(str[Is])... }
    {
    }

    __forceinline decltype(auto) decrypt(void)
    {
        for (size_t i = 0; i < N; ++i) {
            _encrypted[i] = dec(_encrypted[i]);
        }
        _encrypted[N] = '\0';
        return _encrypted.data();
    }
};

//--------------------------------------------------------------------------------
//-- Note: XorStr will __NOT__ work directly with functions like printf.
//         To work with them you need a wrapper function that takes a const char*
//         as parameter and passes it to printf and alike.
//
//         The Microsoft Compiler/Linker is not working correctly with variadic 
//         templates!
//  
//         Use the functions below or use std::cout (and similar)!
//--------------------------------------------------------------------------------

static auto w_printf = [](const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf_s(fmt, args);
    va_end(args);
};

static auto w_printf_s = [](const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf_s(fmt, args);
    va_end(args);
};

static auto w_sprintf = [](char* buf, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsprintf(buf, fmt, args);
    va_end(args);
};

static auto w_sprintf_ret = [](char* buf, const char* fmt, ...) {
    int ret;
    va_list args;
    va_start(args, fmt);
    ret = vsprintf(buf, fmt, args);
    va_end(args);
    return ret;
};

static auto w_sprintf_s = [](char* buf, size_t buf_size, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsprintf_s(buf, buf_size, fmt, args);
    va_end(args);
};

static auto w_sprintf_s_ret = [](char* buf, size_t buf_size, const char* fmt, ...) {
    int ret;
    va_list args;
    va_start(args, fmt);
    ret = vsprintf_s(buf, buf_size, fmt, args);
    va_end(args);
    return ret;
};

//Old functions before I found out about wrapper functions.
//#define XorStr( s ) ( XorCompileTime::XorString< sizeof(s)/sizeof(char) - 1, __COUNTER__, char >( s, std::make_index_sequence< sizeof(s)/sizeof(char) - 1>() ).decrypt() )
//#define XorStrW( s ) ( XorCompileTime::XorString< sizeof(s)/sizeof(wchar_t) - 1, __COUNTER__, wchar_t >( s, std::make_index_sequence< sizeof(s)/sizeof(wchar_t) - 1>() ).decrypt() )

//Wrapper functions to work in all functions below
#define XorStr( s ) []{ constexpr XorCompileTime::XorString< sizeof(s)/sizeof(char) - 1, __COUNTER__, char > expr( s, std::make_index_sequence< sizeof(s)/sizeof(char) - 1>() ); return expr; }().decrypt()
#define XorStrW( s ) []{ constexpr XorCompileTime::XorString< sizeof(s)/sizeof(wchar_t) - 1, __COUNTER__, wchar_t > expr( s, std::make_index_sequence< sizeof(s)/sizeof(wchar_t) - 1>() ); return expr; }().decrypt()

END_NAMESPACE
Lycaonia answered 11/6, 2020 at 0:51 Comment(3)
This is not a thing you should do.Faggoting
Why not? seems like a perfect reason to use it.Lycaonia
Like all DRM schemes, this makes life harder for your legitimate users more than it prevents anything you want to prevent.Faggoting
B
0

While this is an old post, I recently find that __COUNTER__ can effectively generate a C++ namedtuple easily, converting your struct into a tuple. This allows me to implement type reflection like boost::hana, but way more faster.

I post the POC here with several use cases and benchmarks. https://github.com/johnjohnlin/namedtuple

struct S { int x; float y; string z; };
// DEFINE_NAMEDTUPLE(S2)
struct S2 {
    static constexpr int Base = 100 + 1; // __COUNTER__ is 100 here
// NT_MEMBER
    int     x;
    int&    get(integral_constant<unsigned, 101-Base>) { return x; }
// NT_MEMBER
    float   y;
    float&  get(integral_constant<unsigned, 102-Base>) { return y; }
// NT_MEMBER
    string  z;
    string& get(integral_constant<unsigned, 103-Base>) { return z; }
// END_DEFINE_NAMEDTUPLE(S2)
    static constexpr int End = 104;
    static constexpr int num_members = End - Base;
    template<unsigned x> auto& get() { return get(integral_constant<unsigned, x>()); }
};
S2 s_namedtuple;
s_namedtuple.get<1>(); // float, the reference of y
static_assert(sizeof(S2) == sizeof(S)); // namedtuple does not add extra members!
static_assert(sizeof(S2::num_members) == 3u); // namedtuple also provides ::num_members
S2::get_name<1>(); // string("y")
Bisexual answered 19/1, 2023 at 12:56 Comment(0)
D
0

It's also very useful to overcome some of the limitations of C++ enums. Consider e.g. a hierarchy of widgets, each with its own set of specific events, which all must have different IDs (e.g. for using them as keys in a callback map):

template <int N> struct EnumValue { static constexpr int value = N; };

#define UNIQUE_ID EnumValue<__COUNTER__>::value

class Widget {
public:
    enum Event {
        A = UNIQUE_ID,
        B = UNIQUE_ID
    };
};

class Button : public Widget {
public:
    enum Event {
        C = UNIQUE_ID
    };
};


#include <iostream>

int main()
{
    std::cout << (int) Widget::Event::A << ", " << (int) Button::Event::C << "\n";
}

While there are other techniques to achieve (roughly) the same, most of them are more cumbersome/tedious.

Disorient answered 29/6, 2023 at 22:18 Comment(0)
S
-2

I intend to use __COUNTER__ to give every file in our codebase a unique identifier, so that that unique code can be used in logging ASSERTs in an embedded system.

This method is much more efficient than using strings to store filenames (using __FILE__), especially on an embedded system with tiny ROM. I thought about the idea whilst I was reading this article - Assert Yourself on Embedded.com. It's a shame that it only works with GCC-based compilers though.

Sheltonshelty answered 20/1, 2013 at 19:16 Comment(1)
__COUNTER__ gets reset for each cpp file though, so headers would get a different number depending on the order they're included. I think you're misremembering.Darkling

© 2022 - 2024 — McMap. All rights reserved.