Метафункция привязки: принимать как типы, так и параметры шаблона шаблона (принимать что угодно)

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

У меня есть рабочая реализация для простых метафункций шаблона:

template<typename T0, typename T1>
struct MakePair
{
    using type = std::pair<T0, T1>;
};

template<template<typename...> class TF, typename... Ts>
struct Bind
{
    template<typename... TArgs>
    using type = TF<Ts..., TArgs...>;
};

using PairWithInt = typename Bind<MakePair, int>::type;
static_assert(std::is_same<PairWithInt<float>, MakePair<int, float>>{}, "");

Но что, если аргументы шаблона MakePair были шаблонами шаблонов? Или простые числовые значения?

template<template<typename> class T0, template<typename> class T1>
struct MakePair0
{
    using type = /*...*/;
};

template<template<typename...> class TF, template<typename> class... Ts>
struct Bind0 { /*...*/ }

// ...

template<int T0, int T1>
struct MakePair1
{
    using type = /*...*/;
};

template<template<int...> class TF, int... Ts>
struct Bind1 { /*...*/ }

Много ненужного повторения. Это становится неуправляемым, если аргументы шаблона смешиваются между типами, шаблонами шаблонов и целочисленными константами.

Возможно ли что-то вроде следующего фрагмента кода?

template<template<ANYTHING...> class TF, ANYTHING... Ts>
struct BindAnything
{
    template<ANYTHING... TArgs>
    using type = TF<Ts..., TArgs...>;
};

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


person Vittorio Romeo    schedule 17.08.2015    source источник
comment
В чем преимущество чего-то подобного перед простым написанием шаблона? В любом случае это все статическое/время компиляции.   -  person BitTickler    schedule 17.08.2015
comment
Сочинение. Посмотрите на этот вариант использования, который я только что написал, с которым я столкнулся при написании кода. Пример пасти. (тл; др: Rename<Map<Wrapper, TList>, Bind<AllTrue, TPredicate>>)   -  person Vittorio Romeo    schedule 17.08.2015


Ответы (2)


Когда я занимаюсь серьезным метапрограммированием, я превращаю все в типы.

template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;

template<template<class...>class> struct Z {};
template<class Z, class...Ts>
struct apply {};
template<template<class...>class z, class...ts>
struct apply< Z<z>, ts... >:
  tag< z<ts...> >
{};
template<class Z, class...Ts>
using apply_t = type_t< apply<Z, Ts...> >;

теперь мы передаем template<?> foo как Z<foo>, и теперь это тип.

Аналогичные вещи можно сделать для констант, используя std::integral_constant<T, t> (и проще использовать псевдонимы того же самого) или template<class T, T* p> struct pointer_constant {};, превратив их в типы.

Как только все становится типом, ваше метапрограммирование становится более однородным. Шаблоны просто становятся видом типа, с которым apply_t что-то делает.

В C++ нет способа иметь аргумент шаблона, который может быть типом, значением или шаблоном. Так что это лучшее, что вы можете получить.

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

template<class T, class t>
using number_constant = std::integral_constant< T, t{} >;
using number_constant_z = Z<number_constant>;

его аргументы "подняты" от значений к типам, а затем он был обернут Z, чтобы превратиться в тип.

Bind теперь читает:

template<class z, class... Ts>
struct Bind {
  template<class... More>
  using type_base = apply_t< z, Ts..., More... >;
  using type = Z<type_base>;
};
template<class Z, class...Ts>
using Bind_t = type_t<Bind<Z,Ts...>>; // strip ::type
using Bind_z = Z<Bind_t>; // quote into a Z<?>

а Bind_z — это тип, обертывающий шаблон, который возвращает обернутый шаблон и принимает тип, обертывающий шаблон, в качестве первого аргумента.

Чтобы использовать его:

template<class...>struct types{using type=types;};
using types_z=Z<types>;

template<class...Ts>
using prefix =apply_t< Bind_z, types_z, Ts... >;
using prefix_z = Z<prefix>;

prefix_z берет набор типов и генерирует фабрику types<?...>, которая сначала будет содержать префикс Ts....

apply_t< apply_t< prefix_z, int, double, char >, std::string >

is

types< int, double, char, std::string >

живой пример.

Есть еще один забавный подход: делать метапрограммирование в функциях:

template<template<class...>class z, class...Ts>
constexpr auto apply_f( Z<z>, tag<Ts>... )
-> tag<z<Ts...>> { return {}; }

здесь типы представлены значениями типа tag<t>, шаблоны — Z<z>, а значения — std::integral_constant<?>.

Эти двое:

template<class T>
constexpr tag<T> Tag = {};
template<template<class...>class z>
constexpr Z<z> Zag = {};

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

#define TYPEOF(...) type_t<decltype(__VA_ARGS__)>

— это макрос, который переходит от экземпляра tag к типу типа в теге, а Tag<?> перемещается от типа к экземпляру тега.

TYPEOF( apply_f( apply_f( Zag<prefix>, Tag<int>, Tag<double>, Tag<char> ), Tag<std::string> ) )

is

apply_t< apply_t< prefix_z, int, double, char >, std::string >

странно, но может быть интересно.

person Yakk - Adam Nevraumont    schedule 17.08.2015
comment
В C++ нет способа иметь аргумент шаблона, который может быть типом, значением или шаблоном. Перегрузка шаблонов функций может сделать это с одним параметром шаблона. Однако для этого требуется decltype беспорядок. - person dyp; 17.08.2015
comment
@dyp true, и мы могли бы ускориться в стиле хана, выполняя метапрограммирование как constexpr функции. Но даже в этом случае наличие Z полезно, поскольку позволяет передавать шаблон в качестве значения. Решение перегрузки, о котором вы говорите, все еще имеет экспоненциальный взрыв - как написать перегрузку функции, которая принимает шаблон в качестве своего первого аргумента шаблона, каждый аргумент которого (шаблон, переданный в функцию) может быть шаблоном, size_t , int, указатель на функцию или тип? Вы действительно не можете... - person Yakk - Adam Nevraumont; 17.08.2015
comment
Экспоненциальный взрыв рекламы: Вот почему я назвал один параметр шаблона :) Его можно использовать для написания универсального адаптера: decltype(adapt<X>()), где X может быть типом, шаблоном или значением (или функцией, но не набором перегрузки, фиксированного набора подписей). Но из-за беспорядка decltype, вероятно, лучше написать три адаптера с отдельными именами. - person dyp; 17.08.2015
comment
@dyp Предположим, что X — это template<size_t, char, std::string*> Другой X — это template<class, class, class>. Другой - template<class T, T t>. Существует бесконечное количество template<?...> подписей, и нет возможности говорить о них единообразно, чтобы перейти к adapt. Вы должны превратить свои шаблоны в униформу template<class...>. Я допускаю, что макрос #define ADAPT(...) decltype( adapt<__VA_ARGS__>()) мог бы сделать цитирование констант и типов более единообразным (и даже template<class...>). Забавно, но это делает стиль хана более заманчивым, поскольку убивает decltype. - person Yakk - Adam Nevraumont; 17.08.2015
comment
Как только мы получим лямбда-выражения constexpr, использование объектов может даже стать синтаксически приятным подходом: Wm9vwW3qv8JLSApZ -- подождите, я думаю, для этого нам даже не нужны лямбда-выражения constexpr. Все вычисления находятся в невычисленном контексте. - person dyp; 17.08.2015
comment
Я не имел в виду, что вы можете адаптировать шаблоны с нетиповыми параметрами шаблона, используя перегрузки функций. Однако вы можете написать свой Z или мой tag или quote Барри как decltype(adapt<X>()), при этом обобщая adapt для работы не только с шаблонами классов (с параметрами типа), но также со значениями и типами. - person dyp; 17.08.2015
comment
@dyp TYPEOF должен принимать (...), чтобы препроцессор C не понимал < int, double > как часть одного аргумента. Но да, такое метапрограммирование в стиле функций довольно забавно. Однако то, что это constexpr, означает, что вы можете хранить вещи как теги вместо типов дольше и выполнять многострочные вычисления. Я предполагаю, что #define CONSTEXPR(...) tag<TYPEOF(__VA_ARGS__)> также позволяет вам вернуться от выражения, отличного от constexpr, к значению constexpr. - person Yakk - Adam Nevraumont; 17.08.2015
comment
Ах, верно. Вскоре я задумался, почему вы используете ... для одного параметра. gcc поддерживает лямбда-выражения constexpr, но имеет проблемы с лямбда-выражениями в шаблонах переменных. Я подумал, что было бы неплохо использовать функциональные объекты везде, что делает tag лямбда-идентичностью id (конечно, возвращая некоторую оболочку типа). А потом придумали TAGOF, который очень похож на ваш CONSTEXPR. melpon.org/wandbox/permlink/fcCsFNZ1lf3FL9TW - person dyp; 17.08.2015
comment
@dyp template<typename T, typename U> constexpr std::is_same<T, U> operator== (tag<T>, tag<U>) { return {}; } лучше, чем твой ==. Начните думать с типами ‹s›portals‹/s›^H^H^H^H^H^H^H! return в {}, откуда вы пришли. Подождите, нет, это балрог. constexpr это ложь? - person Yakk - Adam Nevraumont; 17.08.2015
comment
оО Что? Zalgo ускользнул от вашего последнего ответа? В любом случае, я рассмотрю это, как только мы исправим integral_constant<bool,.> для правильного использования операторов (IIRC hana уже делает это). В противном случае определение != не будет СУХИМ. - person dyp; 17.08.2015

Думаю, вы ищете здесь quote и map. Во-первых, вам нужно что-то, что с учетом «класса метафункции» и последовательности аргументов дает вам новый тип:

template <typename MCls, typename... Args>
using map = typename MCls::template apply<Args...>;

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

Чтобы превратить шаблон класса в класс метафункции, мы вводим quote:

template <template <typename...> class C>
struct quote {
    template <typename... Args>
    using apply = C<Args...>;
};

Вышеупомянутого достаточно, чтобы сделать что-то вроде:

using T = map<quote<std::tuple>, int, char, double>;

чтобы получить тип:

std::tuple<int, char, double>

В вашем примере мы могли бы написать:

using P = map<quote<MakePair>, int, char>::type; // std::pair<int, char>

но вместо этого я бы предпочел сделать MakePair классом метафункции напрямую:

struct MakePair2 {
    template <typename T, typename U>
    using apply = std::pair<T, U>;
};

using P = map<MakePair2, int, char>; // also std::pair<int, char>

что позволяет избежать лишних ::type.

Последовательно используйте концепции метафункции (тип с определением типа элемента с именем type, например, map) и класса метафункции (тип с псевдонимом шаблона члена с именем apply, например, quote ) и используйте только эти концепции во всем коде метапрограммирования. Ценности и шаблоны классов — это граждане второго сорта.

person Barry    schedule 17.08.2015
comment
Итак, мой Z эквивалентен quote, а мой apply_t вашему map, но, вероятно, вы используете более стандартные имена (map fmap для монад в Haskell? Или это >>=?). Разница в том, что ваш map полагается на T::apply<?...> существующий, а мой полагается на использование Z (и только Z) для переноса шаблона (если я не обновлю свой apply_t, чтобы также обнаружить T::apply<?...>) - person Yakk - Adam Nevraumont; 17.08.2015
comment
@Yakk В основном. Я думаю, основное отличие состоит в том, что map работает с любым классом метафункций, тогда как apply_t работает только с Zs. - person Barry; 17.08.2015
comment
@Yakk Haskell когда-нибудь был у меня, я буду изучать этот список годами, но в основном это fmap. Я не совсем уверен, что делает >>=, поэтому не могу сказать ни того, ни другого. - person Barry; 17.08.2015
comment
nod, и это немного упрощает создание прямых метафункций вместо того, чтобы выполнять 3 шага, требуемых моей системой (сначала структура, которая генерирует тип, затем type_t развернутая, затем Z оболочка ). Однако мне не нравится имя map: для меня map< Z<?>, list<a,b,c> > должно быть list<Z<a>, Z<b>, Z<c>>, а не Z< list<a,b,c> >. - person Yakk - Adam Nevraumont; 17.08.2015
comment
@Yakk apply, вероятно, было бы лучшим именем. Но я предпочитаю quote Z :-P - person Barry; 17.08.2015
comment
Я не совсем уверен, гарантированно ли будет работать распаковка пакета параметров в шаблон псевдонима, который не принимает пакет параметров, см. wg21.cmeerw.net/cwg/issue1430 (поскольку и g++, и clang++ принимают ваш код, я думаю, это не зависит?) - person dyp; 17.08.2015
comment
@dyp, так что MakePair2 может быть незаконным, но quote<MakePair_t> будет законным, потому что он переводит маршалы template<class A, class B> using MakePair_t=typename MakePair<A,B>::type через параметр template<class...>class? - person Yakk - Adam Nevraumont; 18.08.2015
comment
Я не совсем понимаю, как MakePair2 само по себе должно быть незаконным; он не использует никаких пакетов параметров. quote<MakePair2> оба пытаются сопоставить шаблон псевдонима с template<class...>class, а затем заполняют аргументы из пакета параметров. Это может быть незаконным, но здесь нет той же проблемы, что и в примере из CWG 1430: шаблон псевдонима не изменяет список аргументов. Так что я не уверен, применимо ли 1430. (По сути, я имею в виду stackoverflow.com/q/32014484) - person dyp; 18.08.2015