views:

381

answers:

1

I've come up with a solution to a problem but I'm not sure if it'll always work or just on my compiler. First, the problem: I've noticed in a number of situations it's desirable to have a template class that gets reinstantiated each time it's used even when given the same types (say your template class has static members that are initialized to function calls that have some important side effect -- and you want this side effect to be done everytime the template is used). The easy way to do this is to give your template an extra integer parameter:

template<class T, class U, int uniqueify>
class foo
{
...
}

But now you have to manually make sure that everytime you use foo you pass it a different value for uniqueify. The naive solution is to use __LINE__ like this:

#define MY_MACRO_IMPL(line) foo<line>
#define MY_MACRO MY_MACRO_IMPL(__LINE__)

This solution has an issue though -- __LINE__ gets reset for each translation unit. So if two translation units use the template on the same line, the template only gets instantiated once. That may seem unlikely, but imagine how difficult to debug the compiler error it would be if it did happen. Similarly you could try using __DATE__ as a parameter somehow, but that only has seconds precision and it's the time when compiling started, not when it reaches that line, so if you're using a parallel version of make it's rather plausible to have two translation units with the same __DATE__.

Another solution is that some compilers have a special non-standard macro, __COUNTER__ that starts at 0 and increments everytime you use it. But it suffers from the same problem -- it gets reset for each invocation of the preprocessor, so it gets reset each translation unit.

Yet another solution, is to use __FILE__ and __LINE__ together:

#define MY_MACRO_IMPL(file, line) foo<T, U, file, line>
#define MY_MACRO MY_MACRO_IMPL(T, U, __FILE__, __LINE__)

But you can't pass char literals as template parameters according to the standard because they don't have external linkage.

Even if this did work, whether __FILE__ contains the absolute path to the file or just the name of the file itself isn't defined in the standard, so if you had two identical named files in different folders, this could still break. So here is my solution:

#ifndef toast_unique_id_hpp_INCLUDED
#define toast_unique_id_hpp_INCLUDED

namespace {
namespace toast {
namespace detail {

template<int i>
struct translation_unit_unique {
    static int globally_unique_var;
};

template<int i>
int translation_unit_unique<i>::globally_unique_var;

}
}
}

#define TOAST_UNIQUE_ID_IMPL(line) &toast::detail::translation_unit_unique<line>::globally_unique_var
#define TOAST_UNIQUE_ID TOAST_UNIQUE_ID_IMPL(__LINE__)

#endif

Why this works isn't really clear without a usage example, but first an overview. The key insight I had was to see that every time you make a global variable or a static member variable, you're creating a program wide unique number in the form of the address of that variable. So this gives us a unique number that's available at compile time. __LINE__ makes sure we won't get clashes within the same translation unit, and the outer anonymous namespace makes sure the variables are different instances (and thus get differing addresses) across translation units.

Example usage:

template<int* unique_id>
struct special_var
{
    static int value;
}

template<int* unique_id>
int special_var<unique_id>::value = someSideEffect();

#define MY_MACRO_IMPL(unique_id) special_var<unique_id>
#define MY_MACRO MY_MACRO_IMPL(TOAST_UNIQUE_ID)

And foo.cpp becomes:

#include <toast/unique_id.hpp>

...

typedef MY_MACRO unique_var;
typedef MY_MACRO unique_var2;
unique_var::value = 3;
unique_var2::value = 4;
std::cout << unique_var::value << unique_var2::value;

Despite being the same template, and the user providing no differentiating parameters, unique_var and unique_var2 are distinct.

I'm mostly worried about the address in of the variable in the anonymous namespace actually being available at compile time. Technically, an anonymous namespace is like declaring internal linkage, and template parameters can't have internal linkage. But the way the standard says to treat anonymous namespaces is just like the variable was declared as part of a namespace with a program-wide unique name, which means that technically it does have external linkage, even though we don't usually think of it as such. So I think the standard is on my side, but I'm not sure.

I don't know if I've done the best job of explaining why this would be useful, but for the sake of this discussion, it is, I swear ;)

+1  A: 

This should be safe - but simpler way would be to just use FILE. Also, an int isn't enough on a 64-bit platform. Use an intptr_t:

template<const char *file, int line>
class unique_value {
  static char dummy;
  unique_value() { }
public:
  static intptr_t value() { return (intptr_t)&dummy; }
};

#define UNIQUE_VALUE (unique_value<__FILE__, __LINE__>::value())

Further, keep in mind that this will break down when used within a macro or template.

Also, templates with static values with side effects are a bad idea - remember that the side effects occur in arbitrary order, before main() is called - and burying initialization side effects in random functions is not very good for maintainability.

bdonlan
Joseph already considered `__FILE__`, but rejected it because string literals have internal linkage and the macro doesn't necessarily contain the full path, meaning it might not be unique enough. So, are you saying that neither of those concerns is valid?
Rob Kennedy
Right, if you actually try using __FILE__ that way GCC at least will reject it. I think any standards conforming compiler will, but I don't know if other popular compilers, say MSVC, are standards conforming in this respect.
Joseph Garvin