Это скорее концептуальный вопрос. Я пытаюсь найти самый простой способ преобразования шаблона с двумя аргументами (аргументы являются типами) в шаблон с одним аргументом. Т.е., связывающий один из видов.
Это будет эквивалент метапрограммирования 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.
Я думаю, что типичный способ сделать это — сохранить все в мире типов. Не берите шаблоны шаблонов — они грязные. Давайте напишем метафункцию с именем 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>
в качестве упражнения для читателя.
Да, у меня была эта проблема Потребовалось несколько итераций, чтобы найти достойный способ сделать это. По сути, для этого нам нужно указать разумное представление о том, что мы хотим и что нам нужно. Я позаимствовал некоторые аспекты 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");
Все это удается.