C++ 元编程中的模板柯里化

问题描述 投票:0回答:3

这更多的是一个概念性问题。我试图找到将两个参数模板(参数是类型)转换为一个参数模板的最简单方法。即绑定其中一种类型。

这将是相当于 boost/std 中的

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中的代码。

c++ templates metaprogramming bind currying
3个回答
3
投票

我认为执行此操作的典型方法是将所有内容保留在类型世界中。不要采用模板模板——它们很混乱。让我们编写一个名为

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 表达式”的类型,并编写一个名为
lambda
的元函数,它将转发不是 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>
,作为读者的练习。


2
投票

是的,我有这个问题。经过几次迭代才找到一个合适的方法来做到这一点。基本上,要做到这一点,我们需要指定我们想要和需要的合理表示。我借用了

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
投票

柯里化:

template <template <class...> class T, class... Head>
struct curry
{
    template <class... Tail>
    using type = T<Head..., Tail...>;
};

static_assert(curry<std::is_same, int>::type<int>); // true
static_assert(curry<std::is_same, int>::type<float>); // false

© www.soinside.com 2019 - 2024. All rights reserved.