【问题标题】:Currying for templates in C++ metaprogrammingC++ 元编程中的模板柯里化
【发布时间】:2014-11-25 10:45:49
【问题描述】:

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

这将是 boost/std 中 bind 的元编程等价物。我的示例包括一个可能的用例,即将std::is_same 作为模板参数传递给采用单参数模板模板参数(std::is_same 是双参数模板)的模板,即传递给TypeList::FindIfTypeList 在这里没有完全实现,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;
}

点击here在线编译GodBolt中的代码。

【问题讨论】:

标签: c++ templates metaprogramming bind currying


【解决方案1】:

我认为这样做的典型方法是将所有内容保留在类型的世界中。不要使用模板模板——它们很乱。让我们编写一个名为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_exprapply_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_&lt;N&gt; 作为读者的练习。

【讨论】:

    【解决方案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&lt;size_t, N&gt;,我们指定了一些别名:

    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&lt;parms&gt;::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");
    

    所有这些都成功了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-11
      • 1970-01-01
      • 1970-01-01
      • 2017-01-20
      • 2016-06-20
      • 2013-01-13
      • 1970-01-01
      相关资源
      最近更新 更多