Counter for unique types where instances are independent from each other

I am attempting to create a counter for each unique template argument that the counter receives.

For example, I pass an int. The counter returns zero. I pass a float. The counter returns one. I pass an int again. The counter returns zero. Essentially, it generates a unique integer for each templated type.

struct Counter {
    template<typename T>
    int type();
    int tick();
};

template<typename T>
int Counter::type() {
    static int index = tick();
    return index;
}

int Counter::tick() {
    static int index = 0;
    return index++;
}

int main() {
    Counter countera;
    Counter counterb;

    std::cout << countera.type<int>() << std::endl;
    std::cout << countera.type<float>() << std::endl;
    std::cout << countera.type<double>() << std::endl;

    std::cout << counterb.type<float>() << std::endl;
    std::cout << counterb.type<int>() << std::endl;
    std::cout << counterb.type<double>() << std::endl;

    return 0;
}

Above we see an example of this counter. However, there is a problem. If I declare two instances of the counter, the next counter memorizes the order from the previous one. For example, the above code prints the order, 0 1 2 1 0 2. My ideal counter would print 0 1 2 0 1 2. I haven’t explained why here, but for my purposes, this is a problem. The solution here seems to be more templates.

template<typename T, int Version>
int Counter<V>::type() {
    static int index = tick();
    return index;
}

Very similar to the previous code, however there is another template. Now, I can declare my counters like the following.

Counter<0> countera;
Counter<1> counterb;

This solution works, but I don’t want to have to manually type in each number for each instance of the counter. I tried the following.

constexpr inline int tick() {
    static inline int i = 0;
    return i++;
}

template<typename T, int Version = tick()>
int Counter<V>::type() {
    static int index = tick();
    return index;
}

This however does not work as I have a non-constexpr variable inside a constexpr function. Does anyone know of a solution for this? Maybe an example for how I can approach this problem another way?

Thanks

Note: I am doing this for performance reasons. This will be called often and I want to avoid using maps.

Another Note: Integers must be contiguous. They will be used as indices into a deque. Therefore, any typeid() stuff won’t work.

>Solution :

You want the counter values to be computed at compile time, where different instances of Counter have separate counters. The main obstacle here is that it can’t always be determined at compile time whether the counter instance that you’re using is the same as some other counter instance that has already been used. In other words, when you do counter.type<T>(), the result depends on the current counter value of the Counter object that counter refers to. But that itself: the identity of that Counter object: may not be known at compile time. For example:

Counter countera;
Counter counterb;
countera.type<int>();
std::cin >> x;
std::cout << (x ? countera : counterb).type<double>();

The last line forces, at run-time, a check of the current counter value of whichever Counter instance the ternary expression evaluates to, to determine whether to return 1 or 0.

To solve this problem, you can specify which counter instance you want to use, in a manner that is guaranteed to be fixed at compile-time:

struct Counter {};

template <Counter& counter>
int tick() {
    static int index = 0;
    return index++;
}

template <Counter& counter, typename T>
int type() {
    static int index = tick<counter>();
    return index;
}

int main() {
    static Counter countera;
    static Counter counterb;

    std::cout << type<countera, int>() << std::endl;
    std::cout << type<countera, float>() << std::endl;
    std::cout << type<countera, double>() << std::endl;

    std::cout << type<counterb, float>() << std::endl;
    std::cout << type<counterb, int>() << std::endl;
    std::cout << type<counterb, double>() << std::endl;

    return 0;
}

This gives your desired output. Godbolt

Leave a Reply