ID константы времени компиляции - PullRequest
43 голосов
/ 27 сентября 2011

Учитывая следующее:

template<typename T>
class A
{
public:
    static const unsigned int ID = ?;
};

Я хочу, чтобы ID генерировал уникальный идентификатор времени компиляции для каждого T. Я рассмотрел __COUNTER__ и библиотеку boost PP, но до сих пор не удалось.Как я могу добиться этого?

Редактировать: ID должен использоваться, как в случае с оператором переключения

Редактировать2: все ответы, основанные на адресе статического метода или члена, являются неправильными.Несмотря на то, что они создают уникальный идентификатор, они не разрешаются во время компиляции и поэтому не могут использоваться как случаи оператора switch.

Ответы [ 16 ]

1 голос
/ 18 сентября 2016

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


Вы можете определить constexpr функцию, подобную следующей:

static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;

constexpr uint32_t fnv(uint32_t partial, const char *str) {
    return str[0] == 0 ? partial : fnv((partial^str[0])*prime, str+1);
}

inline uint32_t fnv(const char *str) {
    return fnv(offset, str);
}

Затем класс, подобный этому, от которого наследуется:

template<typename T>
struct B {
    static const uint32_t id() {
        static uint32_t val = fnv(T::identifier);
        return val;
    }
};

Идиома CRTP делает все остальное.
Например, вы можете определить производный класс следующим образом:

struct C: B<C> {
    static const char * identifier;
};

const char * C::identifier = "ID(C)";

Пока вы предоставляете разные идентификаторы для разных классов, у вас будут уникальные числовые значения, которые можно использовать для различения типов.

Идентификаторы не обязательно должны быть частью производных классов. Например, вы можете предоставить их с помощью признака:

template<typename> struct trait;
template<> struct trait { static const char * identifier; };

// so on with all the identifiers

template<typename T>
struct B {
    static const uint32_t id() {
        static uint32_t val = fnv(trait<T>::identifier);
        return val;
    }
};

Преимущества:

  • Простота реализации.
  • Нет зависимостей.
  • Числовые значения одинаковы при каждом выполнении.
  • Классы могут использовать один и тот же числовой идентификатор, если это необходимо.

Недостатки:

  • Склонность к ошибкам: копирование и вставка могут быстро стать вашим злейшим врагом.

Это минимальный рабочий пример того, что было описано выше.
Я адаптировал код так, чтобы можно было использовать метод ID member в выражении switch:

#include<type_traits>
#include<cstdint>
#include<cstddef>

static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;

template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I == N), uint32_t>
fnv(uint32_t partial, const char (&)[N]) {
    return partial;
}

template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I < N), uint32_t>
fnv(uint32_t partial, const char (&str)[N]) {
    return fnv<I+1>((partial^str[I])*prime, str);
}

template<std::size_t N>
constexpr inline uint32_t fnv(const char (&str)[N]) {
    return fnv<0>(offset, str);
}

template<typename T>
struct A {
    static constexpr uint32_t ID() {
        return fnv(T::identifier);
    }
};

struct C: A<C> {
    static constexpr char identifier[] = "foo";
};

struct D: A<D> {
    static constexpr char identifier[] = "bar";
};

int main() {
    constexpr auto val = C::ID();

    switch(val) {
    case C::ID():
        break;
    case D::ID():
        break;
    default:
        break;
    }
}

Обратите внимание, что если вы хотите использовать ID в неконстантном выражении, вы должны где-то определить identifier s следующим образом:

constexpr char C::identifier[];
constexpr char D::identifier[];

Как только вы это сделали, вы можете сделать что-то вроде этого:

int main() {
    constexpr auto val = C::ID();
    // Now, it is well-formed
    auto ident = C::ID();

    // ...
}
1 голос
/ 27 сентября 2011

Этого нельзя сделать. Адрес к статическому объекту - самый близкий к уникальному идентификатору, который вы можете получить, однако для того, чтобы получить адреса таких объектов (даже статические константные интегралы), они должны быть где-то определены. Согласно одному правилу определения, они должны быть определены в файле CPP, что невозможно сделать, поскольку они являются шаблонами. Если вы определите статику в заголовочном файле, то каждый модуль компиляции получит свою собственную версию, реализованную, конечно, по разным адресам.

0 голосов
/ 27 октября 2018
template<typename T>
static void get_type_id() { void* x; new (x) T(); }
using type_id_t = void(*)();

отлично работает с оптимизацией

0 голосов
/ 23 сентября 2016

Вот прагматичное решение, если вы можете написать одну дополнительную строку DECLARE_ID(type) для каждого type, который вы хотите использовать:

 #include <iostream>

 template<class> struct my_id_helper;
 #define DECLARE_ID(C) template<> struct my_id_helper<C> { enum {value = __COUNTER__ }; }

 // actually declare ids:
 DECLARE_ID(int);
 DECLARE_ID(double);
 // this would result in a compile error: redefinition of struct my_id_helper<int>’
 // DECLARE_ID(int);

 template<class T>
 class A
 {
 public:
     static const unsigned int ID = my_id_helper<T>::value;
 };

 int main()
 {
     switch(A<int>::ID)
     {
     case A<int>::ID:    std::cout << "it's an int!\n"; break;
     case A<double>::ID: std::cout << "it's a double!\n"; break;
     // case A<float>::ID: // error: incomplete type ‘my_id_helper<float>’
     default: std::cout << "it's something else\n"; break;
     }
 }
0 голосов
/ 01 ноября 2015

Другой альтернативой является рассмотрение следующего класса Data с уникальным статическим полем-членом type:

template <class T>
class Data
{
public:
    static const std::type_index type;
};
// do [static data member initialization](http://stackoverflow.com/q/11300652/3041008)
// by [generating unique type id](http://stackoverflow.com/q/26794944/3041008)
template <class T>
std::type_index const Data<T>::type = std::type_index(typeid(T));

производит вывод (MinGWx64-gcc4.8.4 -std=c++11 -O2)

printf("%s %s\n", Data<int>::type.name(), Data<float>::type.name())
//prints "i f"

Это не совсем целочисленный идентификатор или симпатичная строка для печати, а также constexpr, но его можно использовать в качестве индекса в (не) упорядоченных ассоциативных контейнерах .
Это также работает, еслиЗаголовок Data.h включен в несколько файлов (одинаковые значения hashCode()).

0 голосов
/ 27 сентября 2011

Если приемлемы немонотонные значения и intptr_t:

template<typename T>
struct TypeID
{
private:
    static char id_ref;
public:
    static const intptr_t ID;
};

template<typename T>
  char TypeID<T>::id_ref;
template<typename T>
  const intptr_t TypeID<T>::ID = (intptr_t)&TypeID<T>::id_ref;

Если у вас должны быть целые числа или монотонно увеличивающиеся значения, я думаю, что использование статических конструкторов - единственный путь:

// put this in a namespace
extern int counter;

template<typename T>
class Counter {
private:
  Counter() {
    ID_val = counter++;
  }
  static Counter init;
  static int ID_val;
public:
  static const int &ID;
};

template<typename T>
  Counter<T> Counter<T>::init;
template<typename T>
  int Counter<T>::ID_val;
template<typename T>
  const int &Counter<T>::ID = Counter<T>::ID_val;

// in a non-header file somewhere
int counter;

Обратите внимание, что ни один из этих методов не является безопасным, если вы делитесь ими между общими библиотеками и вашим приложением!

...