5

I have this class:

template <typename T, uint64_t N>
struct Probe {
  static const uint64_t Counter = N;
  typedef T Type;
};

Which I utilize as:

typedef Probe <int, 0> FirstIntProbe;
typedef Probe <int, 1> SecondIntProbe;
typedef Probe <float, 2> FloatProbe;

Is it possible to create a compile time\macro method which allows me to instantiate this class without specifying the second parameter such as:

typedef Probe <int, Something?> FirstIntProbe;
typedef Probe <int, Something?> SecondIntProbe;
typedef Probe <float, Something?> FloatProbe;

I assume this is isn't possible, but then again I've seen people do things in C++ I wouldn't think was possible before.


Update:

  • It is not necessary to increase by one, it's just important that every probe have it's own number.
  • It is not needed to have unique number across different .cpp files\translation units.
Cœur
  • 37,241
  • 25
  • 195
  • 267
Viktor Sehr
  • 12,825
  • 5
  • 58
  • 90
  • 3
    Is it important that values of N start with 0 and always be sequential? And what about the behaviour across translation units? Some compilers have `__COUNTER__`, which may be enough for your purposes. – R. Martinho Fernandes Jan 28 '13 at 10:36

3 Answers3

8

You can look into using the __COUNTER__ macro, which is a compiler extension (but supported on GCC and MSVC, among others). Note that __COUNTER__ is unique only per-translation-unit, i.e. per .cpp file.

edit: Header inclusion in multiple translation units is OK. This example links and runs perfectly fine (built on GCC 4.5):

probe.h:

template <typename T, int N>
struct Probe {
    typedef T Type;
};

#define DECLARE_PROBE(type) typedef struct Probe<type, __COUNTER__>

main.cpp:

#include "test.h"

DECLARE_PROBE(int) intprobe;
DECLARE_PROBE(float) floatprobe;

int main(int argc, char** argv) {
    intprobe ip;
    floatprobe fp;
    return 0;
}

test.cpp:

#include "test.h"

DECLARE_PROBE(int) intprobe;
DECLARE_PROBE(float) floatprobe;

static intprobe ip;
static floatprobe fp;
sheu
  • 5,653
  • 17
  • 32
  • 2
    How can this work? `__COUNTER__` is expanded for each use in each translation unit. So if `Probe` is in a header, and included in more than one tranlation unit, you will have undefined behavior, due to violations of the one definition rule, and even if `Probe` is only present in a single translation unit, if `__COUNTER__` is part of the macro, it will only be expanded once. – James Kanze Jan 28 '13 at 11:12
  • As I mentioned, `__COUNTER__` is only unique per-translation-unit. `__COUNTER__` expands fine in macro use; try `#define DECLARE_PROBE(type) typedef struct Probe` on for size. – sheu Jan 28 '13 at 11:45
  • I've edited the answer with example usage. It's perfectly fine to include the template class and macro definition in a common header. – sheu Jan 28 '13 at 11:56
  • And if I invoke DECLARE_PROBE in two different translation units? – James Kanze Jan 28 '13 at 16:19
  • @JamesKanze: I just did, in the example there. The ODR is fine with multiple definitions, as long as the definitions are identical (section 3.2.5, if you really want to look it up). Think about it -- if it wasn't, your compiler would bug out every time you instantiate `std::vector` in a different translation unit... – sheu Jan 28 '13 at 19:20
  • No, you only declared it in one translation unit. If in one translation unit, I have `DECLARE_PROBE(MyType)`, and in another, `DECLARE_PROBE(YourType)`. In fact, there's no real advantage in `__COUNTER__` over the standard `__LINE__` (Which I use in a similar way in a few, very controlled instances; it is _not_ a general solution to the problem.) – James Kanze Jan 28 '13 at 21:43
  • @JamesKanze: I declared it twice, in `main.cpp` and `test.cpp`. Two different translation units, and with exactly the same template parameters. The ODR hasn't deigned to bite me yet. As to using `__LINE__` over `__COUNTER__`: that is preferable; it just wasn't what the asker was asking for at the time I answered. – sheu Jan 29 '13 at 00:29
5

It's actually very simple if you don't need an integral constant expression (i.e. you're not using Counter as the dimention of an array or such): just use a global static for the counter (or put it in a non-templated base class), and increment it each time you use it in the initialization. Something like:

int currentProbeCounter;

template <typename T>
struct Probe
{
    static int const counter;
    //  ...
};

template <typename T>
const int Probe<T>::counter = ++ currentProbeCounter;

Note that this will only allocate Probe<T>::counter for a given type when (or if) you use it; you might want to use it in the constructors of Probe (even if you don't need to) to ensure its creation. (On the other hand, if you never use it, who cares if it's never created.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 1
    Using an actual static variable for the counter requires the compiler to instantiate storage (and the initialization) for it; this may not be preferable to using a constant template parameter for the counter, which requires no storage and allows the compiler to optimize and fold it into expressions. – sheu Jan 28 '13 at 12:07
  • @sheu But there's no other solution (that I know of) that works. Anything using macros fails as soon as you use multiple translation units. – James Kanze Jan 28 '13 at 16:23
  • Except for the macro above? – sheu Jan 28 '13 at 19:22
  • 1
    @sheu The macro above _only_ works if you limit all of your declarations to the same file. In which case, you might as well just use `__LINE__`, like everyone else, and not bother with non-portable solutions. – James Kanze Jan 28 '13 at 21:44
  • Well, unfortunately I do need a compile time integral constant – Viktor Sehr Jan 29 '13 at 09:14
0

You could use Boost.Preprocessor to do that work for you.

Michael Wild
  • 24,977
  • 3
  • 43
  • 43