Currying for templates in C++ metaprogramming

bind, c++, currying, metaprogramming, templates

Solution

I think the typical way of doing this is keep everything in the world of types. Don't take template templates - they're messy. Let's write a metafunction named `ApplyAnInt` that will take a "metafunction class" and apply `int` to it:

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

Where a simple metafunction class might be just checking if the given type is an `int`:

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

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

Now the goal is to support:

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

We can do that. We're going to call types that contain `_` "lambda expressions", and write a metafunction called `lambda` which will either forward a metafunction class that isn't a lambda expression, or produce a new metafunction if it is:

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;

So we update our original metafunction:

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

Now that leaves two things: we need `is_lambda_expr` and `apply_lambda`. Those actually aren't so bad at all. For the former, we'll see if it's an instantiation of a class template in which one of the types is `_`:

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...> { };

And for `apply_lambda`, we just will substitute the `_` with the given type:

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;
};

And that's all you need actually. I'll leave extending this out to support `arg_<N>` as an exercise to the reader.

Problem

This is more of a conceptual question. I'm trying to find the easiest way of converting a two-arg template (the arguments being types) into a one-arg template. I.e., binding one of the types. This would be the meta-programming equivalent of `bind` in boost/std. My example includes a possible use-case, which is, passing `std::is_same` as template argument to a template that takes a one-arg template template argument (`std::is_same` being a two-arg template), i.e. to `TypeList::FindIf`. The `TypeList` is not fully implemented here, neither is `FindIf`, but you get the idea. It takes a "unary predicate" and returns the type for which that predicate is true, or `void` if not such type. I have 2 working variants but the first is not a one-liner and the 2nd uses a rather verbose `BindFirst` contraption, that would not work for non-type template arguments. Is there a simple way to write such a one-liner? I believe the procedure I'm looking for is called `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; } ``` Click here for code in online compiler GodBolt.

Original source

Related problems