Идентификатор постоянной времени компиляции

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

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

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

Изменить: идентификатор должен использоваться как случай в инструкции switch

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

Ответ 1

Это достаточно, если предположить, что соответствующий стандарту компилятор (по отношению к одному правилу определения):

template<typename T>
class A
{
public:
    static char ID_storage;
    static const void * const ID;
};

template<typename T> char A<T>::ID_storage;
template<typename T> const void * const A<T>::ID= &A<T>::ID_storage;

Из стандарта С++ 3.2.5 Одно правило определения [basic.def.odr] (выделено жирным шрифтом):

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

Ответ 2

Кажется, это работает нормально для меня:

template<typename T>
class Counted
{
  public:
  static int id()
  {
    static int v;
    return (int)&v;
  }
};

#include <iostream>

int main()
{
  std::cout<<"Counted<int>::id()="<<Counted<int>::id()<<std::endl;
  std::cout<<"Counted<char>::id()="<<Counted<char>::id()<<std::endl;

}

Ответ 3

Используйте адрес памяти статической функции.

template<typename T>
class A  {
public:
    static void ID() {}
}; 

(&(A<int>::ID)) будет отличаться от (&(A<char>::ID)) и т.д.

Ответ 4

Недавно я столкнулся с этой точной проблемой. Мое решение:

counter.hpp

class counter
{
    static int i;
    static nexti()
    {
        return i++;
    }
};

Counter.cpp:

int counter::i = 0;

templateclass.hpp

#include "counter.hpp"

    template <class T>
    tclass
    {
        static const int id;
    };

    template <class T>
    int tclass<T>::id = counter::nexti();

Он подходит для корректной работы в MSVC и GCC, за единственным исключением, которое вы не можете использовать в инструкции switch.

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

Ответ 5

Можно создать время HASH компиляции из строки, используя код из этого ответа.

Если вы можете изменить шаблон для включения одного дополнительного целого числа и использовать макрос для объявления переменной:

template<typename T, int ID> struct A
{
    static const int id = ID;
};

#define DECLARE_A(x) A<x, COMPILE_TIME_CRC32_STR(#x)>

Используя этот макрос для объявления типа, элемент id содержит хэш имени типа. Например:

int main() 
{
    DECLARE_A(int) a;
    DECLARE_A(double) b;
    DECLARE_A(float) c;
    switch(a.id)
    {
    case DECLARE_A(int)::id:
        cout << "int" << endl;
        break;
    case DECLARE_A(double)::id:
        cout << "double" << endl;
        break;
    case DECLARE_A(float)::id:
        cout << "float" << endl;
        break;
    };
    return 0;
}

Когда имя типа преобразуется в строку, любая модификация текста имени типа приводит к другому идентификатору. Например:

static_assert(DECLARE_A(size_t)::id != DECLARE_A(std::size_t)::id, "");

Другой недостаток связан с возможностью возникновения столкновения хешей.

Ответ 6

Я обычно использую это:

template<typename>
void type_id(){}

using type_id_t = void(*)();

Так как каждое инстанцирование функции имеет собственный адрес, вы можете использовать этот адрес для идентификации типов:

// Work at compile time
constexpr type_id_t int_id = type_id<int>;

// Work at runtime too
std::map<type_id_t, std::any> types;

types[type_id<int>] = 4;
types[type_id<std::string>] = "values"s

// Find values
auto it = types.find(type_id<int>);

if (it != types.end()) {
    // Found it!
}

Ответ 7

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

Ответ 8

Вот возможное решение, в основном основанное на шаблонах:

#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;
    }
}

Обратите внимание, что для этого требуется С++ 14.

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

constexpr auto id = ID<A, B>;

С этого момента вы можете получить заданный идентификатор для данного типа с помощью метода get:

id.get<A>()

И это все. Вы можете использовать его в инструкции switch в соответствии с запросом и как показано в примере кода.

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

template<typename> struct noLonger { };
constexpr auto id = ID<noLonger<A>, B>;

Это гарантирует, что A больше не ассоциирован с идентификатором, а тот, который указан в B, не изменится.
Если вы не решите удалить A, вы можете использовать что-то вроде:

constexpr auto id = ID<noLonger<void>, B>;

Или что угодно.

Ответ 9

Хорошо..... так что это хак, который я нашел из этого веб-сайта. Он должен работать. Единственное, что вам нужно сделать, это добавить еще один параметр шаблона в ваш struct, который принимает счетчик "метаобъект". Обратите внимание, что A с int, bool и char все имеют уникальные идентификаторы, но не гарантируется, что int будет 1, а bool будет 2 и т.д., Потому что порядок, в котором инициируются шаблоны, не обязательно известен.

Другое примечание:

Это не будет работать с Microsoft Visual С++

#include <iostream>
#include "meta_counter.hpp"

template<typename T, typename counter>
struct A
{
    static const size_t ID = counter::next();
};

int main () {
    typedef atch::meta_counter<void> counter;
    typedef A<int,counter> AInt;
    typedef A<char,counter> AChar;
    typedef A<bool,counter> ABool;
    switch (ABool::ID)
    {
        case AInt::ID:
            std::cout << "Int\n";
            break;
        case ABool::ID:
            std::cout << "Bool\n";
            break;
        case AChar::ID:
            std::cout << "Char\n";
            break;
    }

    std::cout << AInt::ID << std::endl;
    std::cout << AChar::ID << std::endl;
    std::cout << ABool::ID << std::endl;
    std::cout << AInt::ID << std::endl;
    while (1) {}
}

Вот meta_counter.hpp:

// author: Filip Roséen <[email protected]>
// source: http://b.atch.se/posts/constexpr-meta-container

#ifndef ATCH_META_COUNTER_HPP
#define ATCH_META_COUNTER_HPP

#include <cstddef>

namespace atch { namespace {

  template<class Tag>
  struct meta_counter {
    using size_type = std::size_t;

    template<size_type N>
    struct ident {
      friend constexpr size_type adl_lookup (ident<N>);
      static constexpr size_type value = N;
    };

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    template<class Ident>
    struct writer {
      friend constexpr size_type adl_lookup (Ident) {
        return Ident::value;
      }

      static constexpr size_type value = Ident::value;
    };

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    template<size_type N, int = adl_lookup (ident<N> {})>
    static constexpr size_type value_reader (int, ident<N>) {
      return N;
    }

    template<size_type N>
    static constexpr size_type value_reader (float, ident<N>, size_type R = value_reader (0, ident<N-1> ())) {
      return R;
    }

    static constexpr size_type value_reader (float, ident<0>) {
      return 0;
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    template<size_type Max = 64>
    static constexpr size_type value (size_type R = value_reader (0, ident<Max> {})) {
      return R;
    }

    template<size_type N = 1, class H = meta_counter>
    static constexpr size_type next (size_type R = writer<ident<N + H::value ()>>::value) {
      return R;
    }
  };
}}

#endif /* include guard */

Ответ 10

Используя этот счетчик постоянных выражений:

template <class T>
class A
{
public:
    static constexpr int ID() { return next(); }
};
class DUMMY { };
int main() {
    std::cout << A<char>::ID() << std::endl;
    std::cout << A<int>::ID() << std::endl;
    std::cout << A<BETA>::ID() << std::endl;
    std::cout << A<BETA>::ID() << std::endl;
    return 0;
}

вывод: (GCC, С++ 14)

1
2
3
3

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

Ответ 11

Если допустимы немонотонные значения и a 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;

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

// 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;

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

Ответ 12

Другой альтернативой является рассмотрение следующего класса 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, но может быть использоваться как индекс в (un) упорядоченных ассоциативных контейнерах.
Он также работает, если заголовок Data.h включен в несколько файлов (те же значения hashCode()).

Ответ 13

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


Вы можете определить функцию 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 в инструкции 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, как следует:

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

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

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

    // ...
}

Ответ 14

Вот код С++, который использует макросы __DATE__ и __TIME__ для получения уникальных идентификаторов для типов <T>

Формат:

// __DATE__ "??? ?? ????"
// __TIME__ "??:??:??"

Это хеш-функция низкого качества:

#define HASH_A 8416451
#define HASH_B 11368711
#define HASH_SEED 9796691    \
+ __DATE__[0x0] * 389        \
+ __DATE__[0x1] * 82421      \
+ __DATE__[0x2] * 1003141    \
+ __DATE__[0x4] * 1463339    \
+ __DATE__[0x5] * 2883371    \
+ __DATE__[0x7] * 4708387    \
+ __DATE__[0x8] * 4709213    \
+ __DATE__[0x9] * 6500209    \
+ __DATE__[0xA] * 6500231    \
+ __TIME__[0x0] * 7071997    \
+ __TIME__[0x1] * 10221293   \
+ __TIME__[0x3] * 10716197   \
+ __TIME__[0x4] * 10913537   \
+ __TIME__[0x6] * 14346811   \
+ __TIME__[0x7] * 15485863

unsigned HASH_STATE = HASH_SEED;
unsigned HASH() {
    return HASH_STATE = HASH_STATE * HASH_A % HASH_B;
}

Использование хэш-функции:

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

template <>
const unsigned int A<float>::ID = HASH();

template <>
const unsigned int A<double>::ID = HASH();

template <>
const unsigned int A<int>::ID = HASH();

template <>
const unsigned int A<short>::ID = HASH();

#include <iostream>

int main() {
    std::cout << A<float>::ID << std::endl;
    std::cout << A<double>::ID << std::endl;
    std::cout << A<int>::ID << std::endl;
    std::cout << A<short>::ID << std::endl;
}

Ответ 15

Вот прагматичное решение, если вам нравится писать одну дополнительную строку 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 an int!\n"; break;
     case A<double>::ID: std::cout << "it a double!\n"; break;
     // case A<float>::ID: // error: incomplete type ‘my_id_helper<float>’
     default: std::cout << "it something else\n"; break;
     }
 }