Альтернативные генераторы id для типов

В моем проекте у меня есть генератор идентификаторов для типов, которые выглядят примерно так:

class Family {
    static std::size_t identifier;

    template<typename...>
    static std::size_t family() {
        static const std::size_t value = identifier++;
        return value;
    }

public:
    template<typename... Type>
    inline static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

std::size_t Family::identifier{};

Использование:

const auto id = Family::type<FooBar>();

Он отлично подходит для моих целей, но имеет некоторые ограничения. Наиболее раздражающим (цель вопроса) является то, что он не работает при использовании исполняемым файлом, который ссылается на разделяемые библиотеки, если все они пытаются создать идентификаторы. Обычно результат состоит в том, что n-й идентификатор присваивается разным типам через границы, потому что каждая разделяемая библиотека поддерживает свой отдельный Family::identifier.

Некоторые ребята из разделяемой библиотеки отметили, что более надежное решение было бы оценено, но не предложило тот, который не испортил производительность (почти все они представляют контейнеры, обнаруживают функциональные возможности и распределение памяти).

Существует ли альтернативный подход, который работает с вышеупомянутыми ограничениями, не теряя при этом производительности текущего проекта?

Я искал SO и нашел интересные ответы. Многим из них было несколько лет. Я хотел бы исследовать решения до последней версии стандарта вместо этого, если интерфейс существующего класса остается неповрежденным.
Это самое интересное. Он использует адреса статических членов для достижения того же, но не соответствует идее последовательно генерируемых идентификаторов

Примечание: использование RTTI, к сожалению, не является вариантом.

Примечание: идентификаторы должны генерироваться последовательно и начиная с 0, как в представленном выше решении.

Ответ 1

Ваша проблема возникает, потому что у вас есть эта строка в файле заголовка:

std::size_t Family::identifier{};

Поэтому он попадает в каждую единицу перевода. Вместо этого вам нужно переместить хранилище для этого в исходный файл.cpp, который компилируется только один раз, возможно, в собственную общую библиотеку. Тогда в программе будет только один экземпляр identifier, и он будет работать так, как вы планируете.

Вы также можете перенести identifier из static переменной класса в глобальную extern в заголовочном файле (и, как указано выше, определить его в одном файле.cpp).

Если у вас есть С++ 17 или более поздняя версия, вы также можете попробовать:

inline std::size_t Family::identifier{};

Хотя язык не гарантирует (или даже упоминает), что происходит, когда вы используете эту новую функцию на границах разделяемых библиотек, она работает на моей машине.

Ответ 2

Если вы не требуете, чтобы идентификаторы были последовательными целыми числами, вы можете использовать адрес статического члена шаблона в качестве идентификатора. Преимущество этого подхода заключается в том, что он не требует инициализации во время выполнения (использует статическую инициализацию):

// in a header
class Family {
    template<class...> struct Id { static char const id; };

    template<typename... T>
    static std::size_t family() {
        return reinterpret_cast<std::size_t>(&Id<T...>::id);
    }

public:
    template<typename... Type>
    static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
int main() {
    auto int_id = Family::type<int>();
    auto int_int_id = Family::type<int, int>();
}

Вы также можете сделать этот id константой времени компиляции и использовать ее в качестве аргумента шаблона:

// in a header
struct Family {
    template<class...> struct Id { static char const id; };
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
template<char const*>
struct X {};

int main() {
    X<&Family::Id<int>::id> x;
}

Ответ 3

если вам не нужны последовательные идентификаторы, используйте адрес функции как идентификатор.

template<typename... T>
uintptr_t getID() {
    return reinterpret_cast<uintptr_t>(&getID<T...>);
}

а потом

auto intID = getID<int>();
auto floatID = getID<float>();
...