2

Background

I am tinkering with polymorphic serialization and deserialization in C++. For that purpose I use a static map: [type id-string] -> [type factory function]. Each type has to be registered in this map and I would like to do it at compile-time.

Approach

The naïve approach is:

/// Creates a concrete serializable type provided as a template parameter
template <typename T>
ISerializable* createSerializable() { return new T; }

/// Factory that registers a serializable type T
template <typename T>
struct SerializableFactory
{
    SerializableFactory(const char* type_name)
    {
        // registerType adds a type_name->factory_function entry to the map
        registerType(type_name, createSerializable<T>);
    }
};

Registering the types is done with the macro:

/// Macro that registers the type at compile-time using a static factory instance
#define REGISTER_TYPE(T)                                                   \
    static SerializableFactory<T> global_##T##Factory(#T);

For example REGISTER_TYPE(ArbitraryClass) will become:

static SerializableFactory<ArbitraryClass> 
    global_ArbitraryClassFactory("ArbitraryClass");

Problem

Unfortunately this will not work for ArbitraryClass<int> bacause <, > are not allowed to be used in identifier.

Question

Is there a good work-around to achieve registering arbitrary template type this way?

Alternatives

I considered the following alternatives (each has disadvantages):

  • Registering types run-time: looks less elegant, requires more effort from the serialization user;
  • RTTI: requires RTTI to be enabled, gives no guarantees that different types will have always different hashes/names (very unlikely of course, but still);
  • Asking the user to provide a type alias or alias name: less elegant, more effort from the serialization user

Update:

  • As @Svalorzen mentioned in his answer anonymous namespace can be used. Unfortunately, not being able to use the macro more than once in a translation unit is a disadvantage. E.g., for header-only types.
AMA
  • 4,114
  • 18
  • 32
  • Have you considered using `__COUNTER__` macro instead of `T` to generate factory instance unique name? I know it's not in standard but definitely could do the trick here... – W.F. Feb 27 '17 at 10:32
  • @W.F. no, I did not. Thanks for the suggestion. Yes, being non-standard is a disadvantage. If there are no side-effects, and it is supported by three major compilers, who knows :) – AMA Feb 27 '17 at 10:42

3 Answers3

2

A partial solution would be to always use the same name, but wrap it in an unnamed namespace. This would only allow you to register a single type per translation unit, but maybe that's good enough.

/// Macro that registers the type at compile-time using a static factory instance
#define REGISTER_TYPE(T)                                         \
    namespace {                                                  \
        static SerializableFactory<T> serial_global_factory(#T); \
    }

Otherwise, you could use the __LINE__ and __FILE__ macro tokens to create a unique name for your object - as long as you don't have to reference it anywhere else. There are others, a list can be found here.

Svalorzen
  • 5,353
  • 3
  • 30
  • 54
  • Thank you for the answer. Sorry, I forgot to mention this alternative in my question. I will extend it. Yes, being not able to use the macro in the same translation unit can be quite a disadvantage for the user. E.g., header-only types. – AMA Feb 27 '17 at 10:44
  • @AMA Another option, which I've added to my answer and is similar to what W.F. said, is to use any preprocessor token which allows you to create an unique identifier. – Svalorzen Feb 27 '17 at 13:13
  • Thank you for the suggestion. I was thinking about it. `__FILE__` macro is not very useful because it can contain arbitrary symbols. But then it hit me: unnamed namespace + `__LINE__` could do it. I've added my own answer. However if you feel that this is what you were suggesting, please extend your answer and I will happily accept it :) – AMA Feb 28 '17 at 09:42
  • @AMA Depends on what you need to do with that symbol. If it is just to add a hook to register a type, it does not matter what its name is. And in any case, if you need to use it again later, you could also add a pointer to it to your map to find it. – Svalorzen Feb 28 '17 at 09:47
  • Yes, in my case I only need to register the type. – AMA Feb 28 '17 at 09:49
  • @AMA Then why do you care what its name is? =) – Svalorzen Feb 28 '17 at 09:49
  • @AMA Ah I see what you mean. Arbitrary symbols may mean that it won't work as an identifier. Makes sense. – Svalorzen Feb 28 '17 at 09:50
  • User may want to register two types in the same translation unit, so identifiers can't be the same (they should be unique). Nothing else I care about :) – AMA Feb 28 '17 at 09:51
1

I had an aha! moment inspired by comment from @W.F. , @Svalorzen answer, and this answer. I believe it is quite a clever trick, which has none of alternative's disadvantages.


Solution: using an unnamed/anonymous namespace and adding __LINE__ to the identifier should always give a unique identifier (unless the macro is used twice at the same line).


Here's how it looks:

#define MERGE(A, B) A##B
#define CREATE_UNIQUE_IDENTIFIER(line) MERGE(unique_identifier_on_line_, line)
/// UNIQUE_IDENTIFIER generates identifiers like:
/// "unique_identifier_on_line_" + line_number
#define UNIQUE_IDENTIFIER CREATE_UNIQUE_IDENTIFIER(__LINE__)
/// Macro that registers the type at compile-time using a static factory instance
#define REGISTER_TYPE(T)                                                   \
    namespace                                                              \
    {                                                                      \
    static SerializableFactory<T> UNIQUE_IDENTIFIER(#T);                   \
    }
Community
  • 1
  • 1
AMA
  • 4,114
  • 18
  • 32
0

Try to define the type as a single name and use the name:

typedef ArbitraryClass<int> ArbitraryClass_int_;
REGISTER_TYPE(ArbitraryClass_int_);

You could also try to put it in a hash-map, where the key is the typename.

cmdLP
  • 1,658
  • 9
  • 19
  • Thanks for the answer. But your suggestion is what I meant by *Asking the user to provide an alias name* in alternatives. – AMA Feb 27 '17 at 10:20
  • You'll also have problems, if you define them inside templates, causing the string will always be `"T"` for example. `typeid(T).name()` gets a real individual string, but shorten. You could expand this to the representation by library-functions. If you want to make it more dynamically, try to use a preprogressor, eg. written in python. – cmdLP Feb 27 '17 at 10:23