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 ]

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

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

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;

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

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

9 голосов
/ 22 сентября 2016

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

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!
}
4 голосов
/ 17 сентября 2016

Можно сгенерировать 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, "");

Еще один недостаток связан с возможностью возникновения коллизии хэшей.

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

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

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 голосов
/ 27 сентября 2011

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

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.

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

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

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

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

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

2 голосов
/ 30 ноября 2016

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

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, C ++ 14)

1
2
3
3

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

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

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

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

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

#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 <filip.roseen@gmail.com>
// 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 */
1 голос
/ 22 сентября 2016

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

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

Обратите внимание, что для этого требуется C ++ 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>;

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

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

Вот код C ++, который использует макросы __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;
}
...