Карринг для шаблонов в метапрограммировании C ++

Это скорее концептуальный вопрос. Я пытаюсь найти самый простой способ преобразования шаблона с двумя аргументами (аргументы являются типами) в шаблон с одним аргументом. Т.е., связывающий один из видов.

Это будет эквивалент метапрограммирования bind в буст / станд. Мой пример включает в себя возможный вариант использования, который заключается в передаче std::is_same в качестве аргумента шаблона для шаблона, который принимает аргумент шаблона из одного аргумента (std::is_same являющийся шаблоном с двумя аргументами), т.е. TypeList::FindIf, TypeList не полностью реализован здесь, ни FindIf, Но ты получил идею. Он принимает «унарный предикат» и возвращает тип, для которого этот предикат является истинным, или void если бы не такой тип.

У меня есть 2 рабочих варианта, но первый не однострочный, а второй использует довольно многословный BindFirst хитрое, это не будет работать для не типовых аргументов шаблона. Есть ли простой способ написать такой однострочник? Я считаю, что процедура, которую я ищу, называется currying,

#include <iostream>

template<template<typename, typename> class Function, typename FirstArg>
struct BindFirst
{
template<typename SecondArg>
using Result = Function<FirstArg, SecondArg>;
};

//template<typename Type> using IsInt = BindFirst<_EqualTypes, int>::Result<Type>;
template<typename Type> using IsInt = std::is_same<int, Type>;struct TypeList
{
template<template<typename> class Predicate>
struct FindIf
{
// this needs to be implemented, return void for now
typedef void Result;
};
};

int main()
{

static_assert(IsInt<int>::value, "");
static_assert(!IsInt<float>::value, "");// variant #1: using the predefined parameterized type alias as predicate
typedef TypeList::FindIf<IsInt>::Result Result1;

// variant #2: one-liner, using BindFirst and std::is_same directly
typedef TypeList::FindIf< BindFirst<std::is_same, int>::Result>::Result Result2;

// variant #3: one-liner, using currying?
//typedef TypeList::FindIf<std::is_same<int, _>>::Result Result2;

return 0;
}

Нажмите Вот для кода в онлайн-компиляторе GodBolt.

11

Решение

Я думаю, что типичный способ сделать это — сохранить все в мире типов. Не берите шаблоны шаблонов — они грязные. Давайте напишем метафункцию с именем ApplyAnInt который возьмет «класс метафункций» и применить int к нему:

template <typename Func>
struct ApplyAnInt {
using type = typename Func::template apply<int>;
};

Где простой класс метафункций может просто проверять, является ли данный тип int:

struct IsInt {
template <typename T>
using apply = std::is_same<T, int>;
};

static_assert(ApplyAnInt<IsInt>::type::value, "");

Теперь цель состоит в том, чтобы поддержать:

static_assert(ApplyAnInt<std::is_same<_, int>>::type::value, "");

Мы можем это сделать. Мы будем называть типы, которые содержат _ «лямбда-выражения», и написать метафункцию под названием lambda который либо перенаправит класс метафункции, который не является лямбда-выражением, либо создаст новую метафункцию, если это:

template <typename T, typename = void>
struct lambda {
using type = T;
};

template <typename T>
struct lambda<T, std::enable_if_t<is_lambda_expr<T>::value>>
{
struct type {
template <typename U>
using apply = typename apply_lambda<T, U>::type;
};
};

template <typename T>
using lambda_t = typename lambda<T>::type;

Итак, мы обновляем нашу оригинальную метафункцию:

template <typename Func>
struct ApplyAnInt
{
using type = typename lambda_t<Func>::template apply<int>;
};

Теперь это оставляет две вещи: нам нужно is_lambda_expr а также apply_lambda, Это на самом деле не так уж и плохо. Для первого мы посмотрим, является ли это экземпляром шаблона класса, в котором один из типов _:

template <typename T>
struct is_lambda_expr : std::false_type { };

template <template <typename...> class C, typename... Ts>
struct is_lambda_expr<C<Ts...>> : contains_type<_, Ts...> { };

И для apply_lambdaмы просто заменим _ с данным типом:

template <typename T, typename U>
struct apply_lambda;

template <template <typename...> class C, typename... Ts, typename U>
struct apply_lambda<C<Ts...>, U> {
using type = typename C<std::conditional_t<std::is_same<Ts, _>::value, U, Ts>...>::type;
};

И это все, что вам нужно на самом деле. Я оставлю расширение этого, чтобы поддержать arg_<N> в качестве упражнения для читателя.

3

Другие решения

Да, у меня была эта проблема Потребовалось несколько итераций, чтобы найти достойный способ сделать это. По сути, для этого нам нужно указать разумное представление о том, что мы хотим и что нам нужно. Я позаимствовал некоторые аспекты std::bind() в этом я хочу указать шаблон, который я хочу привязать, и параметры, которые я хочу привязать к нему. Затем, внутри этого типа, должен быть шаблон, который позволит вам передавать набор типов.

Так что наш интерфейс будет выглядеть так:

template <template <typename...> class OP, typename...Ts>
struct tbind;

Теперь наша реализация будет иметь эти параметры плюс контейнер типов, которые будут применены в конце:

template <template <typename...> class OP, typename PARAMS, typename...Ts>
struct tbind_impl;

Наш базовый случай даст нам тип шаблона, который я назову ttype, это возвратит шаблон содержащихся типов:

template <template <typename...> class OP, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>>
{
template<typename...Us>
using ttype = OP<Ss...>;
};

Тогда у нас есть случай перемещения следующего типа в контейнер и ttype обратитесь к ttype в немного более простом базовом случае:

template <template <typename...> class OP, typename T, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, T, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, std::tuple<Ss..., T>
, Ts...
>::template ttype<Us...>;
};

И, наконец, нам нужно переназначить шаблоны, которые будут переданы ttype:

template <template <typename...> class OP, size_t I, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, std::integral_constant<size_t, I>, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, typename std::tuple<
Ss...
, typename std::tuple_element<
I
, typename std::tuple<Us...>
>::type
>
, Ts...
>::template ttype<Us...>;

Теперь, поскольку программисты ленивы и не хотят печатать std::integral_constant<size_t, N> для каждого переназначаемого параметра мы указываем несколько псевдонимов:

using t0 = std::integral_constant<size_t, 0>;
using t1 = std::integral_constant<size_t, 1>;
using t2 = std::integral_constant<size_t, 2>;
...

О, почти забыл реализацию нашего интерфейса:

template <template <typename...> class OP, typename...Ts>
struct tbind : detail::tbind_impl<OP, std::tuple<>, Ts...>
{};

Обратите внимание, что tbind_impl был помещен в detail Пространство имен.

И вуаля, tbind!

К сожалению, существует дефект до c ++ 17. Если вы пройдете tbind<parms>::ttype к шаблону, который ожидает шаблон с определенным числом параметров, вы получите ошибку, так как количество параметров не совпадает (конкретное число не совпадает ни с одним числом). Это немного усложняет вещи, требующие дополнительного уровня косвенности. 🙁

template <template <typename...> class OP, size_t N>
struct any_to_specific;

template <template <typename...> class OP>
struct any_to_specific<OP, 1>
{
template <typename T0>
using ttype = OP<T0>;
};

template <template <typename...> class OP>
struct any_to_specific<OP, 2>
{
template <typename T0, typename T1>
using ttype = OP<T0, T1>;
};
...

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

Пример использования:

static_assert(!tbind<std::is_same, float, t0>::ttype<int>::value, "failed");

static_assert( tbind<std::is_same, int  , t0>::ttype<int>::value, "failed");

static_assert(!any_to_specific<
tbind<std::is_same, float, t0>::ttype
, 1
>::ttype<int>::value, "failed");

static_assert( any_to_specific<
tbind<std::is_same, int  , t0>::ttype
, 1
>::ttype<int>::value, "failed");

Все это удается.

0