3

I would like a generic way to create unique compile-time identifiers for any C++ user defined types.
for example:

unique_id<my_type>::value == 0 // true
unique_id<other_type>::value == 1 // true  

I've managed to implement something like this using preprocessor meta programming, the problem is, serialization is not consistent. For instance if the class template unique_id is instantiated with other_type first, then any serialization in previous revisions of my program will be invalidated.

I've searched for solutions to this problem, and found several ways to implement this with non-consistent serialization if the unique values are compile-time constants. If RTTI or similar methods, like boost::sp_typeinfo are used, then the unique values are obviously not compile-time constants and extra overhead is present. An ad-hoc solution to this problem would be, instantiating all of the unique_id's in a separate header in the correct order, but this causes additional maintenance and boilerplate code, which is not different than using an enum unique_id{my_type, other_type};.

A good solution to this problem would be using user-defined literals, unfortunately, as far as I know, no compiler supports them at this moment. The syntax would be 'my_type'_id; 'other_type'_id; with udl's.

I'm hoping somebody knows a trick that allows implementing serialize-able unique identifiers in C++ with the current standard (C++03/C++0x), I would be happy if it works with the latest stable MSVC and GNU-G++ compilers, although I expect if there is a solution, it's not portable.

I would like to make clear, that using mpl::set or similar constructs like mpl::vector and filtering, does not solve this problem, because the scope of the meta-set/vector is limited and actually causes more problems than just preprocessor meta programming.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Endiannes
  • 53
  • 5

3 Answers3

2

A while back I added a build step to one project of mine, which allowed me to write @script_name(args) in a C++ source file and have it automatically replaced with the output of the associated script, for instance ./script_name.pl args or ./script_name.py args.

You may balk at the idea of polluting the language into nonstandard C++, but all you'd have to do is write @sha1(my_type) to get the unique integer hash of the class name, regardless of build order and without the need for explicit instantiation.

This is just one of many possible nonstandard solutions, and I think a fairly clean one at that. There's currently no great way to impose an arbitrary, consistent ordering on your classes without just specifying it explicitly, so I recommend you simply give in and go the explicit instantiation route; there's nothing really wrong with centralising the information, but as you said it's not all that different from an enumeration, which is what I'd actually use in this situation.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • This seems like a reasonable solution. I will resort to this until udl's are implemented in major compilers. – Endiannes Jan 10 '11 at 11:38
1

Persistence of data is a very interesting problem.

My first question would be: do you really want serialization ? If you are willing to investigate an alternative, then jump to the next section.

If you're still there, I think you have not given the typeid solution all its due.

// static detection
template <typename T>
size_t unique_id()
{
  static size_t const id = some_hash(typeid(T)); // or boost::sp_typeinfo
  return id;
}

// dynamic detection
template <typename T>
size_t unique_id(T const& t)
{
  return some_hash(typeid(t)); // no memoization possible
}

Note: I am using a local static to avoid the order of initialization issue, in case this value is required before main is entered

It's pretty similar to your unique_id<some_type>::value, and even though it's computed at runtime, it's only computed once, and the result (for the static detection) is then memoized for future calls.

Also note that it's fully generic: no need to explicitly write the function for each type.


It may seem silly, but the issue of serialization is that you have a one-to-one mapping between the type and its representation:

  • you need to version the representation, so as to be able to decode "older" versions
  • dealing with forward compatibility is pretty hard
  • dealing with cyclic reference is pretty hard (some framework handle it)
  • and then there is the issue of moving information from one to another --> deserializing older versions becomes messy and frustrating

For persistent saves, I usually recommend using a dedicated BOM. Think of the saved data as a message to your future self. And I usually go the extra mile and proposes the awesome Google Proto Buffer library:

  • Backward and Forward compatibility baked-in
  • Several format outputs -> human readable (for debug) or binary
  • Several languages can read/write the same messages (C++, Java, Python)
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

Pretty sure that you will have to implement your own extension to make this happen, I've not seen nor heard of any such construct for compile-time. MSVC offers __COUNTER__ for the preprocessor but I know of no template equivalent.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • He's already implemented the template equivalent, which is fairly easy to do, and neither that nor `__COUNTER__` is helpful in imposing an ordering across files/classes. – Jon Purdy Jan 10 '11 at 00:45