CRTP and unique persistent identifiers
Asked Answered
M

2

1

Consider the following code:

#include <iostream>
#include <cstdlib>
#include <ctime>

struct BaseClass {
    static int identifier() {
        static int identifier_counter = 0;
        return identifier_counter++;
    }
};

template <class D>
struct Class: public BaseClass {
    static int identifier() {
        static int class_identifier = BaseClass::identifier();
        return class_identifier;
    }
};

struct A: public Class<A> { };
struct B: public Class<B> { };

int main() {
    std::srand(std::time(0));
    int r = std::rand()%2;

    if(r) {
        std::cout << "A: " << A::identifier() << std::endl;
        std::cout << "B: " << B::identifier() << std::endl;
    } else {
        std::cout << "B: " << B::identifier() << std::endl;
        std::cout << "A: " << A::identifier() << std::endl;
    }
}

It's a reduced, but still plausible representation of the problem.

Any derived class will have a specific, different identifier on runtime and two instances of the same type will share the same identifier. Surely a good solution for such a problem.

Unfortunately, those identifiers depend on the order on which the identifier members are invoked (we can see it easily by running multiple times the example). In other words, given two classes A and B, if it happens that running twice the software their identifier members are invoked in different order, they have different identifiers.

My problem is that, for some reasons, I need to store those identifiers somewhere and let them survive the single execution, so that I can reason on the original types once the application runs once more and decide to read those values from the storage.

An alternative would be to use hash_code from type_info, but it suffers from other problems. Another solution would be to force the calls to the identifier members during the bootstrap of the application, but this one also has several drawbacks.

I'd like to know if there is so far an easy to implement though still elegant solution that is completely transparent to the developer to identify types over several executions, as the one above is for the single run of the application.

Mendacity answered 30/11, 2015 at 21:13 Comment(0)
Z
2

The problem of having unique persistent identifier for every class is unsolvable with C++. Sorry. You will either depend on the order of calling your initializaer functions, or, if you call them from initializers of static objects, on the order of static initializer (which will usually depend on the order of your object files in your link line).

And of course, there is no guarantee that hash will be unique.

You will have to use external script for this. In particular, something like this might be used:

// when class is first created
class Foo {
  static int class_id = ?CLASS_ID?;
};

// after class is process by the script 
class Foo {
  static int class_id = 123; // Autogenerated by 'stamp_id.pl'
};

You might have a perl script running as part of the compilation (the very first thing) which opens all .h files in the project directory, reads all of them, counts all instances of Autogenerated by 'stamp_id.pl' and than stamps all ?CLASS_ID? with incremented counter (starting from the number of already generated ids). To add some safety, you might want a better pattern than simple ?...?, but I think, you got the idea.

Zaller answered 30/11, 2015 at 21:24 Comment(3)
What do you mean by external script? Something like to store the identifiers in a file to be loaded when the software is executed?Mendacity
Something similar to the way rcs ids are used, for example. You mark your class with that special attribute when first created, and a script modifies source when the value is not set. It is part of the compilation, not run-time environment.Zaller
Sounds interesting, for I cannot load anything during runtime. I'm sorry for my lack of knowledge about how rcs works, but can you integrate your response with some links to explain better this approach? Thank you anyway, really appreciated the suggestion. :-)Mendacity
M
1

Even if they are slightly different as questions, here I proposed a solution that maybe can fit well also with this question.
It isn't based on the CRTP idiom and it has the advantage of being a non-intrusive solution.

It follows a minimal, working example:

#include<cstddef>
#include<functional>
#include<iostream>

template<typename T>
struct wrapper {
    using type = T;
    constexpr wrapper(std::size_t N): N{N} {}
    const std::size_t N;
};

template<typename... T>
struct identifier: wrapper<T>... {
    template<std::size_t... I>
    constexpr identifier(std::index_sequence<I...>): wrapper<T>{I}... {}

    template<typename U>
    constexpr std::size_t get() const { return wrapper<U>::N; }
};

template<typename... T>
constexpr identifier<T...> ID = identifier<T...>{std::make_index_sequence<sizeof...(T)>{}};

// ---

struct A {};
struct B {};

constexpr auto id = ID<A, B>;

int main() {
    switch(id.get<B>()) {
    case id.get<A>():
        std::cout << "A" << std::endl;
        break;
    case id.get<B>():
        std::cout << "B" << std::endl;
        break;
    }
}

The main problem is that the ids can change if an element is removed from the types list.
Anyway, it's trivial to define an empty placeholder to work around the issue.

Mendacity answered 24/9, 2016 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.