трансляторЯ хотел бы отобразить некоторые массивы из контейнера шаблонов в аргументы функции в соответствии с определенным порядком, определенным индексами, хранящимися в списке шаблонов с переменными параметрами (я не могу придумать более простой способ определения проблемы).
Массивы хранятся с использованием void*
но безопасность типов между массивами и параметрами функции гарантируется вспомогательным классом. Этот же вспомогательный класс должен расширять заданные пакеты параметров, извлекать соответствующие массивы, связывать их с указателем функции и вызывать функцию. Вот где я застрял.
подробно: Я заранее прошу прощения за длинный вопрос и размещение кода, который не компилируется, но я старался быть как можно более кратким.
Проблема заключается в отображении правильных членов контейнера в объект функтора. Контейнер имеет список массивов, определенных TypeList, чья реализация похожа на этот.
Для простоты мы предполагаем вспомогательные объекты typelist TLAlg::length<TL>
а также TLAlg::TypeAt
определены и позволяют пользователям получить доступ к длине списка типов и N-го типа соответственно.
Контейнерный класс выделяет массив для каждого типа в списке типов (называемом полем) и сохраняет непрозрачный указатель на эти буферы. Безопасный геттер реализован для доступа к определенному индексу поля. Его реализация заключается в следующем:
// container class, stores an array for each type in the typelist
template<class TL>
class Volume {
public:
// list of opaque buffers
void *opaque_buffers[TLAlg::length<TL>::value];
template<int size>
Volume(const size_t (&dim)[size]){
// each opaque_buffers is initialized here
...
}
// getters are using the field index for type-safety
template <int index> typename
TLAlg::TypeAt<TL, index>::Result &
get(const std::initializer_list<size_t> &position);
};
Мы хотим реализовать Functor
объект, который будет применять данную функцию к объему с использованием определенного подмножества списка типов. Вместо того, чтобы манипулировать массивами напрямую, пользователь дает список индексов полей, к которым он хочет получить доступ, и указатель на функцию, которую нужно применить. Объект functor отвечает за установку правильных аргументов.
Чтобы повысить безопасность типов, мы разделяем их в два списка: только чтение и чтение / запись (чтение const
и не const
). Данный прототип функции должен соответствовать определению объекта функтора: код компилируется только в том случае, если указатель на данную функцию точно соответствует определению аргумента, поэтому нам не нужно беспокоиться о несовпадении типов. Реализация функтора:
template<typename TL, class T1, class T2> struct Functor{};
template< typename TL,
template<size_t...> class T1, size_t... A1, // read only arguments
template<size_t...> class T2, size_t... A2 // read-write arguments
>
struct Functor< TL, T1<A1...>, T2<A2...> >{
// type of the function pointer
typedef void (*Type)(const typename TLAlg::TypeAt<TL, A1>::Result* ... ,
typename TLAlg::TypeAt<TL, A2>::Result* ...);
Functor(Volume<TL> &v, Type f): f(f){
// At this point we have everything we need: the volume, the function
// and the list of arguments, but how to combine them all?
// maybe some magic here to map the opaque pointers to the arguments?
}
void operator()(){
// or maybe here?
}
}
Как видите, в настоящий момент функтор ничего не делает, потому что я не знаю, как сопоставить два пакета параметров с массивами контейнеров и связать их с указателем функции …
Для ясности, вот пример использования для класса functor:
// main Typelist
typedef Typelist<float, Typelist<double, Typelist<int, NullType>>> Pixel;
// function we want to apply: reads first and last field of the list and updates second
void foo(const float *f1,
const int *f3,
double *f2){}
// helper class to store the parameter packs
template<size_t ... T> struct Pack {};
int main(){
// volume declaration
Volume<Pixel> volume((size[]){1024,1024});
// delare typesafe functor
Functor<Pixel, // typelist
Pack<0,2>, // list of read-only fields
Pack<1> // list of read-write fields
> apply_foo(volume, foo);
apply_foo(); // <- this does nothing at the moment
}
Я пытался играть с std::forward
а также std::bind
долгое время, но пока не могу найти правильное решение. Подставляя список типов для std::tuple
может быть рассмотрено, но предпочтительно сохранить текущее определение.
Этот код может выглядеть странным и излишне сложным, но это очень упрощенная версия массивной среды, в которой использование этих классов имеет смысл.
Любая помощь будет высоко оценен.
Пояснения к ответу Якка:
Мне нужен список типов, потому что я делаю в нем больше магии, например, каждый элемент списка может быть кортежем вместо одного типа, чтобы связать имя. Это позволяет аккуратный код, такой как:
typedef MakeTypeList((float, p),
(float, bnd),
(float, gosa)) HimenoFields;
// I can now use aliases, and the declaration order does not matter in the code.
// here is an access to the second field:
volume.get<HimenoFields::bnd>({0,0,0});
Вы можете представить, как это очень хорошо сочетается с операциями, которые я хочу реализовать с помощью функторов.
Во-вторых, я понимаю, почему вы запутались в добытчике. Как я уже говорил, это очень упрощенная версия кода, несмотря на очень длинный вопрос. В реальной программе объемы являются многомерными и либо сплющиваются в одном массиве, либо распределяются в многомерном массиве, поэтому для геттера требуются полные координаты. Существует несколько реализаций этих геттеров с разными параметрами.
Наконец, Functor не нужно знать, к какому элементу применить функцию, потому что он сам контролирует пространство итерации и применяет предопределенный скелет (т. Е. Трафарет, волновой фронт …). Опять же, я пропустил это ради простоты.
Во-первых, я бы переписал ваш type_list
:
template<typename... Ts>
struct type_list {};
с переменным типом вместо вашего хака с 18 аргументами. Пишу type_at<n>::type
а также index_of<T>::value
тогда не сложно Сопоставление между ними и вашей парной TypeList
тоже не сложно
template<typename list>
struct make_TypeList;
template<typename T0, typename T1, typename...Ts>
struct make_TypeList<type_list<T0, T1, Ts...>> {
typedef typename make_Typelist< type_list<T1, Ts...> >::type tail;
typedef TypeList< T0, tail > type;
};
template<typename T0>
struct make_TypeList<type_list<T0>> {
typedef TypeList< T0, NullType > type;
};
template<>
struct make_TypeList<type_list<>> {
typedef NullType type;
};
если тебе это действительно нужно Есть причины работать со списками, не относящимися к типам, но вы не демонстрируете их.
Создать коллекцию индексов типов во время компиляции немного сложно, но если вы передадите верхнюю границу, вы сможете это сделать. Цель состоит в том, чтобы создать последовательность:
template<size_t... s>
struct seq {};
Если вы получаете эти индексы во время компиляции, это проще. Как только у вас есть эта последовательность, и у вас есть type_at
Вы можете написать вызывающую функцию вроде этого:
template<size_t... s, typename... Ts>
void evaluate( seq<s...> unused, void(*func)(Ts*... ts) ) {
func( &get_at<s>()... );
}
где мы распаковываем последовательность непосредственно в вызов функции. Как это часто бывает, рассматриваемая последовательность является просто 0,1,2,3,4,...,n-1
, который может быть легко сгенерирован:
template<size_t max, size_t... s>
struct make_seq:make_seq<max-1, max-1, s...> {};
template<size_t... s>
struct make_seq<0, s...> {
typedef seq<s...> type;
};
Чтобы было ясно: operator()
вызывает вспомогательную функцию после выполнения make_seq<sizeof...(Ts)>::type()
, передавая это во вспомогательную функцию, которая затем вызывает func( &get_at<s>(/*n maybe?*/)... )
и боб твой дядя
Меня смущает одна вещь:
// getters are using the field index for type-safety
template <int index> typename
TLAlg::TypeAt<TL, index>::Result &
get(const std::initializer_list<size_t> &position);
Я не уверен почему const std::initializer_list<size_t> &position
нужно, или, по крайней мере, почему у вас нет:
template <int index> typename
TLAlg::TypeAt<TL, index>::Result &
get_at(size_t n);
что заставляет меня думать, что ваш operator()
в вашем функторе отсутствует «к какому индексу применить этот функтор», если ваш Volume
это массив нескольких типов.
Но я сильно подозреваю, что «создайте пакет индексов, вызовите вспомогательную функцию, чтобы вы могли разобраться, а затем разверните пакет в вызове функции» — это трюк, который вам не хватает.
Других решений пока нет …