Шаблон метапрограммы, преобразующий тип в уникальный номер - PullRequest
11 голосов
/ 10 ноября 2009

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

int myInt = TypeInt<AClass>::value;

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

Я хочу знать, возможно ли это вообще, и в таком случае как. Потому что, хотя я многому научился в изучении этого вопроса, я так и не смог найти ответ.

(П.С. Ответ да / нет гораздо более приятен, чем решение на С ++, которое не использует метапрограммирование, так как это область, которую я изучаю)

Ответы [ 9 ]

7 голосов
/ 21 марта 2010

Это делает то, что вы хотите. Значения присваиваются по необходимости. Используется способ назначения статики в функциях.

inline size_t next_value()
{
     static size_t id = 0;
     size_t result = id;
     ++id;
     return result;
}

/** Returns a small value which identifies the type.
    Multiple calls with the same type return the same value. */
template <typename T>
size_t get_unique_int()
{
     static size_t id = next_value();
     return id;
}

Это не шаблонное метапрограммирование на стероидах, но я считаю это хорошей вещью (поверьте мне!)

7 голосов
/ 10 ноября 2009

В принципе, это возможно, хотя решение, вероятно, не то, что вы ищете.

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

template< typename T >
struct type2int
{
   // enum { result = 0 }; // do this if you want a fallback value
};

template<> struct type2int<AClass> { enum { result = 1 }; };
template<> struct type2int<BClass> { enum { result = 2 }; };
template<> struct type2int<CClass> { enum { result = 3 }; };

const int i = type2int<T>::result;

Если вы не предоставите реализацию отката в базовом шаблоне, это произойдет с ошибкой для неизвестных типов, если T, в противном случае будет возвращено значение отката.

В зависимости от вашего контекста, могут быть и другие возможности. Например, вы можете определить эти числа внутри самих типов:

class AClass {
  public:
    enum { inta_val = 1 };
  // ...
};

class BClass {
  public:
    enum { inta_val = 2 };
  // ...
};

// ... 

template< typename T >
struct type2int
{
   enum { result = T::int_val }; // will fail for types without int_val
};

Если вы дадите больше контекста, могут быть и другие решения.

Редактировать :

На самом деле больше нет контекста. Я изучал, возможно ли это на самом деле, но без назначения самих номеров.

Я думаю, что идея Майка о порядке - хороший способ сделать это (опять же, для фиксированного набора типов ) без необходимости явно присваивать числа: они неявно задаются порядком. Тем не менее, я думаю, что это будет проще, используя список типов. Индекс любого типа в списке будет его номером. Я думаю, что может сделать что-то вроде следующего:

// basic type list manipulation stuff
template< typename T1, typename T2, typename T3...>
struct type_list;

// meta function, List is assumed to be some instance of type_list
template< typename T, class List >
struct index_of {
  enum { result = /* find index of T in List */ };
};

// the list of types you support
typedef type_list<AClass, BClass, CClass> the_type_list;

// your meta function
template< typename T >
struct type2int
{
   enum { result = index_of<T, the_type_list>::result };
};
6 голосов
/ 06 апреля 2015

Аналогично подходу Майкла Андерсона, но эта реализация полностью соответствует стандартам и может быть выполнена во время компиляции. Начиная с C ++ 17, похоже, что значения constexpr будет разрешено использовать в качестве параметра шаблона для других целей метапрограммирования шаблона. Кроме того, unique_id_type можно сравнивать с ==,! =,>, <И т. Д. Для сортировки. </p>

// the type used to uniquely identify a list of template types
typedef void (*unique_id_type)();

// each instantiation of this template has its own static dummy function. The
// address of this function is used to uniquely identify the list of types
template <typename... Arguments>
struct IdGen {
   static constexpr inline unique_id_type get_unique_id()
   {
      return &IdGen::dummy;
   }

private:
   static void dummy(){};
};
3 голосов
/ 10 ноября 2009

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

template <class Prev, class This>
class TypeList
{
public:
   enum
   {
      position = (Prev::position) + 1,
   };
};

template <>
class TypeList<void, void>
{
public:
  enum
  {
     position = 0,
  };
};


#include <iostream>

int main()
{
        typedef TypeList< void, void> base;  // base
        typedef TypeList< base, double> t2;  // position is unique id for double
        typedef TypeList< t2, char > t3; // position is unique id for char

        std::cout << "T1 Posn: " << base::position << std::endl;
        std::cout << "T2 Posn: " << t2::position << std::endl;
        std::cout << "T3 Posn: " << t3::position << std::endl;

}

Это работает, но, естественно, я бы не хотел как-то указывать тип prev. Желательно придумать способ отследить это автоматически. Может быть, я поиграю с этим еще немного, чтобы посмотреть, возможно ли это. Определенно интересная / веселая головоломка.

2 голосов
/ 30 сентября 2013

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

struct Dummy
{
};

template<typename>
struct TypeDummy
{
    static const Dummy value;
};

template<typename T>
const Dummy TypeDummy<T>::value = Dummy();

typedef const Dummy* TypeId;

template<typename T, TypeId p = &TypeDummy<T>::value>
struct TypePtr
{
    static const TypeId value;
};

template<typename T, TypeId p>
const TypeId TypePtr<T, p>::value = p;

struct A{};

struct B{};

const TypeId typeA = TypePtr<A>::value;
const TypeId typeB = TypePtr<B>::value;

Я разработал это как обходной путь для проблем с производительностью при использовании типов упорядочения с использованием typeid(A) == typeid(B), который определенному компилятору не удается оценить во время компиляции. Также полезно иметь возможность хранить TypeId значений для сравнения во время выполнения: например, someType == TypePtr<A>::value

2 голосов
/ 10 ноября 2009

Я думаю, что это возможно сделать для фиксированного набора типов, но довольно много работы. Вам нужно будет определить специализацию для каждого типа, но должна быть возможность использовать утверждения времени компиляции для проверки уникальности. Я предполагаю STATIC_ASSERT(const_expr), как в Boost.StaticAssert, которое вызывает ошибку компиляции, если выражение ложно.

Предположим, у нас есть набор типов, для которых нам нужны уникальные идентификаторы - всего 3 для этого примера:

class TypeA;
class TypeB;
typedef int TypeC;

Нам понадобится способ сравнения типов:

template <typename T, typename U> struct SameType
{
    const bool value = false;
};

template <typename T> struct SameType<T,T>
{
    const bool value = true;
};

Теперь мы определим порядок всех типов, которые мы хотим перечислить:

template <typename T> struct Ordering {};

template <> struct Ordering<void>
{
    typedef TypeC prev;
    typedef TypeA next;
};

template <> struct Ordering<TypeA>
{
    typedef void  prev;
    typedef TypeB next;
};

template <> struct Ordering<TypeB>
{
    typedef TypeA prev;
    typedef TypeC next;
};

template <> struct Ordering<TypeC>
{
    typedef TypeB prev;
    typedef void  next;
};

Теперь мы можем определить уникальный идентификатор:

template <typename T> struct TypeInt
{
    STATIC_ASSERT(SameType<Ordering<T>::prev::next, T>::value);
    static int value = TypeInt<T>::prev::value + 1;
};

template <> struct TypeInt<void>
{
    static int value = 0;
};

ПРИМЕЧАНИЕ. Я не пытался скомпилировать ничего из этого. Может потребоваться добавление typename в нескольких местах, и оно может вообще не работать.

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

1 голос
/ 21 марта 2010

Это может быть связано с некоторыми "плохими вещами" и, возможно, нарушением стандарта некоторыми тонкими способами ... но я подумаю, что я все равно поделюсь ... может, кто-то еще может очистить его до 100% законности? Но, похоже, он работает на моем компиляторе.

Логика такова: создайте статическую функцию-член для каждого интересующего вас типа и получите ее адрес. Затем преобразуйте этот адрес в int. Биты, которые немного подозрительны: 1) функция преобразования ptr в int. и 2) я не уверен, что стандарт гарантирует, что адреса статических функций-членов все будут правильно объединены для использования в разных блоках компиляции.

typedef void(*fnptr)(void);

union converter
{
  fnptr f;
  int i;
};

template<typename T>
struct TypeInt
{
  static void dummy() {}
  static int value() { converter c; c.f = dummy; return c.i; }
};

int main()
{
  std::cout<< TypeInt<int>::value() << std::endl;
  std::cout<< TypeInt<unsigned int>::value() << std::endl;
  std::cout<< TypeInt< TypeVoidP<int> >::value() << std::endl;
}
0 голосов
/ 21 ноября 2011

type2int, поскольку постоянная времени компиляции невозможна даже в C ++ 11. Может быть, какой-нибудь богатый парень должен пообещать награду за ответ? До этого я использую следующее решение, которое в основном совпадает с решением Мэтью Херрмана:

class type2intbase {
    template <typename T>
    friend struct type2int;

    static const int next() {
        static int id = 0; return id++;
    }
};

template <typename T>
struct type2int {
    static const int value() {
        static const int id = type2intbase::next(); return id;
    }
};

Примечание также

template <typename T>
struct type2ptr {
    static const void* const value() {
        return typeid(T).name();
    }
};
0 голосов
/ 10 ноября 2009

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

...