Как я могу построить вектор constexpr типов, который может добавлять типы?

#c #metaprogramming #c 20 #template-meta-programming

Вопрос:

Мой коллега бросил мне вызов построить a TypeVector , который является векторным контейнером времени компиляции, который может возвращать типы и удалять типы во время компиляции. С учетом выражения constexp TypeVector вы должны иметь возможность запрашивать фактический тип из элемента TypeVector .

ПРИМЕР ПСЕВДОКОДА ИСПОЛЬЗОВАНИЕ типовектора:

 consteval auto build_vector() {
   TypeVector<2> x; //TypeVector of size 2
   x.push_back(int);
   x.push_back(float);
   return x;
}

static constexpr auto my_vector = build_vector();
// Get type from first element of my_vector 
typename retrieve_type<my_vector.get<0>()>::type some_variable = 0; // equivalent to int some_variable = 0; 

// Get type from second element of my_vector 
typename retrieve_type<my_vector.get<1>()>::type some_variable2 = 0.0f; // equivalent to float some_variable2 = 0.0f; 

 

Я думаю, что лучший способ сделать это-использовать std::array
Проблема в том, что std::array нельзя хранить типы, но необходимо хранить значения одного и того же типа, поэтому я преобразую типы в значения следующим образом:

 template <typename T>
struct TypeHash{
   static constexpr char obj; // Create one obj per type
   static constexpr char* value = amp;obj; // This will be different for each type
   
};
template <typename T>
static constexpr char* to_value = TypeHash<T>::value;

// Now I can do to_value<int> and to_value<float> to "store" the float and int as values of type const char*
// Example usage:

template <int N>
using TypeVector<N> = std::array<const char*, N>;
consteval auto some_func() {
   TypeVector<2> x;
   x[0] = to_value<int>;
   x[1] = to_value<float>;
   return x;
}

static constexpr auto first_type = some_func()[0]; 

 

Проблема заключается в обратном выполнении этой операции: создании retrieve_type из псевдокода.

Я думал о создании retrieve_type const char* карты для ввода с помощью специализаций шаблонов, таких как:

 static constexpr auto first_type = some_func()[0]; // From above 

template < const char* value> struct retrieve_type {};
template <> struct retrieve_type<to_value<int>> { using type = int; };
template <> struct retrieve_type<to_value<float>> { using type = float; };


// Now I can get the actual type from first type:
typename retrieve_type<first_type>::type y = 0; // int x = 0;


 

С этим подходом есть несколько проблем:

  1. Я должен создать retrieve_type специализацию для каждого существующего типа ( не огромная проблема, я могу справиться с этим, но это не идеально).
  2. (Большая проблема) Как я могу создать retrieve_type специализации для шаблонов??
 template <typename T>
struct SomeTemplateClass {};
// How to create a specialisation of retrieve_type for all instantiations of SomeTemplateClass?
 

Как я могу заставить это работать для типов, созданных на основе шаблона? Могу ли я избежать ручного создания retrieve_type специализации для каждого типа?

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

Комментарии:

1. @ Yksisarvinen Смысл TypeVector в том, чтобы добавлять типы во время компиляции.

2. Если я правильно вас понимаю, то то, что вы ищете, называется типографским списком. Таких примеров вокруг довольно много.

3. @Pepijn Kramer TypeVector должен иметь возможность добавлять типы, в отличие от списка типов.

4. Шаблон может быть сопоставлен типу, который является функцией для типов.

5. @Yakk-AdamNevraumont Не могли бы вы уточнить? Я не совсем понял.

Ответ №1:

Шаблоны могут быть биектически сопоставлены типам.

 template<class T>
strict tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag={};

template<template<class...>class Z>
struct ztemplate{
  template<class...Ys>
  constexpr auto operator()(tag_t<Ys>...)const{
    return tag<Z<Ys...>>;
  }
};
 

Это обрабатывает одну категорию шаблонов.

Если вы решите проблему со значением для ввода карты, вы можете решить проблему со значением для шаблона. И с учетом вышесказанного шаблон — это просто объект функции constexpr без состояния в тегах типов.

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

Что касается ввода значения, это просто долгая медленная многословная работа.

Вы можете построить дерево синтаксического анализа в плоском списке целых чисел.

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

 template<std::size_t I>
using index_t=std::integral_constant<std::size_t,I>;
template<std::size_t I>
constexpr index_t<I> index{};    
constexpr tag_t<int> map_to_type(index_t<0>){return {};}
constexpr ztemplate<std::vector> map_to_type(index_t<1>){return {};}
constexpr auto make_type(){
  constexpr int arr[]={1,0};
  return map_to_type(index<arr[0]>)(map_to_type(index<arr[1]>));
}
constexpr auto tag=make_type();
decltype(tag)::type vec={1,2,3};
 

Ответ №2:

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

Если у вас есть какая-то конструкция, в которой хранятся типы, и вы хотите извлечь из нее тип, чтобы вы могли выполнять обычные действия с этим «значением» (объявлять переменные этого типа и т. Д.), То вы должны сделать это таким образом, чтобы это работало в рамках ограничений статически типизированного языка.

Это означает, что конструкция, в которой хранятся эти типы, должна быть типом. Не объект, а реальный тип. Нет способа обойти этот факт: если вы хотите быть в состоянии сделать тип-расчеты, для того, чтобы результат этой операции должен быть тип, источник этой операции также должны быть типа ( constexpr функции вам играть в игры где можно пройти «типы» в качестве значений параметров через объект-обертка, но это всего лишь уловка аргумент шаблона вычет; первичный расчет сам по видам).

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

Всегда.

Таким образом, мы должны рассматривать значения типов с точки зрения функционального программирования. В частности, значения типов являются неизменяемыми «объектами». Какой бы ценностью они ни обладали, это та ценность, которой они обладают.

Это как число 2. Вы можете увеличить число 2, но это не изменит «2». Он создает новое число 3, которое отличается от 2. «2» все еще существует даже после того, как вы увеличили его.

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

В C нет другого способа получить «значения», представляющие типы, которые можно каким-либо образом извлечь. По крайней мере, пока C не получит некоторую форму отражения.

Ваша попытка обойти это с помощью «хэшей» умна, но она просто перемещает неизменяемые данные.

Вместо неизменяемого списка типов у вас есть неизменяемый реестр типов. Этот реестр должен быть конечным. Он также сопоставляет числа и типы. И шаблоны-это не типы; это мета-функции, которые генерируют типы. std::vector не является типом; std::vector<int> является типом, сгенерированным std::vector метафункцией.

Функция сопоставления между числами и типами не может сопоставляться с метафункциями генерации между числами и типами. C , будучи статически типизированным языком, не допускает этого.


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

Но этот список также является чем-то другим: конечным.

В C шаблон-это мета-функция, которая генерирует материал: функции, переменные или типы. «Тип шаблона» на самом деле не является чем-то особенным. std::vector это не тип; это вещь, которая генерирует типы на основе набора параметров. std::vector<int> это тип, генерируемый std::vector метафункцией.

Если у вас есть сопоставление между числами и типами, то оно сопоставляется между числами и типами, а не метафункциями типов. Точно так же , как если у вас есть параметр функции, который принимает значение an int , эту функцию нельзя заставить принимать функцию, которая возвращает значение an int (вы могли бы создать новую функцию, которая делает это, но не обращайте внимания на это сейчас).

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


В C доступны десятки таких библиотек. Повышать.Hana обладает функциями списка типов, как и многие другие библиотеки. Но все они будут иметь ограничения C .

Но вы можете выполнить базовый поиск типов на a std::tuple . std::tuple_element_t<I, tpl> возвращает тип типа I th tpl и std::tuple_size_v<tpl> вычисляет количество элементов tpl . И базовая функциональность добавления тоже не сложна:

 template<typename Tpl, typename T>
struct append_tuple; //No definition.

template<typename ...Types, typename T>
struct append_tuple<std::tuple<Types...>, T>
{
  using type = std::tuple<Types..., T>;
};

template<typename Tpl, typename T>
using append_tuple_t = typename append_tuple<Tpl, T>::type;
 

И нет, вы не сможете (легко) сопоставить имя строки с типом.

Комментарии:

1. Я понимаю, что типы неизменяемы. Однако настоящая проблема не в этом. Проблема в том, что, учитывая идентификатор constexpr для типа (который может быть символом*, или int, или чем угодно), Я не могу эффективно извлечь фактический тип, кроме как с помощью некоторого переключения типов с помощью специализации шаблона.

2. @SomeProgrammer: Я добавил пример, в котором я использую std::tuple в качестве специального списка типов.

3. Да, я понимаю, как работают списки типов, но мне нужен контейнер, в который я мог бы добавлять типы на месте.

4. @SomeProgrammer: Я потратил около 20 абзацев, подробно объясняя, что вы не можете этого сделать и почему вы не можете этого сделать. Если вы хотите добавить что-то, оно должно создать новое значение типа. Период.

5. Постоянное вычисляемое значение может использоваться для определения типа переменной или выражения. template<int>struct foo{}; template<0> struct foo{using type=int;}; typename foo<0>::type x=0; сопоставляет целое число времени компиляции с типом. Возникает вопрос, как сохранить эту ассоциацию без необходимости делать это вручную. (Шаблоны могут быть сопоставлены типам, поэтому решение карты типов значений также решает шаблоны)