Тип преобразования метапрограмм шаблона в уникальный номер

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

int myInt = TypeInt<AClass>::value;

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

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

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

Ответ 1

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

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

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

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

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

Ответ 3

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

// 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(){};
};

Ответ 4

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

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

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

Ответ 5

Я думаю, что это можно сделать для фиксированного набора типов, но довольно много работы. Вам нужно будет определить специализацию для каждого типа, но должно быть возможно использовать утверждения времени компиляции для проверки уникальности. Я предполагаю 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 в несколько мест, и это может вообще не работать.

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

Ответ 6

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

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

Ответ 7

Это может делать некоторые "плохие вещи" и, вероятно, нарушает стандарт каким-то тонким образом... но я думал, что я поделюсь в любом случае. Может быть, кто-то еще может дезинформировать его на что-то на 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;
}

Ответ 8

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

Ответ 9

type2int, поскольку константа времени компиляции невозможна даже в С++ 11. Может быть, какой-нибудь богатый парень должен обещать награду за anwser? До тех пор я использую следующее решение, которое в основном равно Matthew Herrmann's:

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();
    }
};