How can currying be done in C++?

前端 未结 10 1343
余生分开走
余生分开走 2020-11-28 03:39

What is currying?

How can currying be done in C++?

Please Explain binders in STL container?

相关标签:
10条回答
  • 2020-11-28 04:16

    I implemented currying with variadic templates as well (see Julian's answer). However, I did not make use of recursion or std::function. Note: It uses a number of C++14 features.

    The provided example (main function) actually runs at compile time, proving that the currying method does not trump essential optimizations by the compiler.

    The code can be found here: https://gist.github.com/Garciat/c7e4bef299ee5c607948

    with this helper file: https://gist.github.com/Garciat/cafe27d04cfdff0e891e

    The code still needs (a lot of) work, which I may or may not complete soon. Either way, I'm posting this here for future reference.

    Posting code in case links die (though they shouldn't):

    #include <type_traits>
    #include <tuple>
    #include <functional>
    #include <iostream>
    
    // ---
    
    template <typename FType>
    struct function_traits;
    
    template <typename RType, typename... ArgTypes>
    struct function_traits<RType(ArgTypes...)> {
        using arity = std::integral_constant<size_t, sizeof...(ArgTypes)>;
    
        using result_type = RType;
    
        template <size_t Index>
        using arg_type = typename std::tuple_element<Index, std::tuple<ArgTypes...>>::type;
    };
    
    // ---
    
    namespace details {
        template <typename T>
        struct function_type_impl
          : function_type_impl<decltype(&T::operator())>
        { };
    
        template <typename RType, typename... ArgTypes>
        struct function_type_impl<RType(ArgTypes...)> {
            using type = RType(ArgTypes...);
        };
    
        template <typename RType, typename... ArgTypes>
        struct function_type_impl<RType(*)(ArgTypes...)> {
            using type = RType(ArgTypes...);
        };
    
        template <typename RType, typename... ArgTypes>
        struct function_type_impl<std::function<RType(ArgTypes...)>> {
            using type = RType(ArgTypes...);
        };
    
        template <typename T, typename RType, typename... ArgTypes>
        struct function_type_impl<RType(T::*)(ArgTypes...)> {
            using type = RType(ArgTypes...);
        };
    
        template <typename T, typename RType, typename... ArgTypes>
        struct function_type_impl<RType(T::*)(ArgTypes...) const> {
            using type = RType(ArgTypes...);
        };
    }
    
    template <typename T>
    struct function_type
      : details::function_type_impl<typename std::remove_cv<typename std::remove_reference<T>::type>::type>
    { };
    
    // ---
    
    template <typename Args, typename Params>
    struct apply_args;
    
    template <typename HeadArgs, typename... Args, typename HeadParams, typename... Params>
    struct apply_args<std::tuple<HeadArgs, Args...>, std::tuple<HeadParams, Params...>>
      : std::enable_if<
            std::is_constructible<HeadParams, HeadArgs>::value,
            apply_args<std::tuple<Args...>, std::tuple<Params...>>
        >::type
    { };
    
    template <typename... Params>
    struct apply_args<std::tuple<>, std::tuple<Params...>> {
        using type = std::tuple<Params...>;
    };
    
    // ---
    
    template <typename TupleType>
    struct is_empty_tuple : std::false_type { };
    
    template <>
    struct is_empty_tuple<std::tuple<>> : std::true_type { };
    
    // ----
    
    template <typename FType, typename GivenArgs, typename RestArgs>
    struct currying;
    
    template <typename FType, typename... GivenArgs, typename... RestArgs>
    struct currying<FType, std::tuple<GivenArgs...>, std::tuple<RestArgs...>> {
        std::tuple<GivenArgs...> given_args;
    
        FType func;
    
        template <typename Func, typename... GivenArgsReal>
        constexpr
        currying(Func&& func, GivenArgsReal&&... args) :
          given_args(std::forward<GivenArgsReal>(args)...),
          func(std::move(func))
        { }
    
        template <typename... Args>
        constexpr
        auto operator() (Args&&... args) const& {
            using ParamsTuple = std::tuple<RestArgs...>;
            using ArgsTuple = std::tuple<Args...>;
    
            using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
    
            using CanExecute = is_empty_tuple<RestArgsPrime>;
    
            return apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...);
        }
    
        template <typename... Args>
        constexpr
        auto operator() (Args&&... args) && {
            using ParamsTuple = std::tuple<RestArgs...>;
            using ArgsTuple = std::tuple<Args...>;
    
            using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
    
            using CanExecute = is_empty_tuple<RestArgsPrime>;
    
            return std::move(*this).apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...);
        }
    
    private:
        template <typename... Args, size_t... Indices>
        constexpr
        auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) const& {
            using ParamsTuple = std::tuple<RestArgs...>;
            using ArgsTuple = std::tuple<Args...>;
    
            using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
    
            using CurryType = currying<FType, std::tuple<GivenArgs..., Args...>, RestArgsPrime>;
    
            return CurryType{ func, std::get<Indices>(given_args)..., std::forward<Args>(args)... };
        }
    
        template <typename... Args, size_t... Indices>
        constexpr
        auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) && {
            using ParamsTuple = std::tuple<RestArgs...>;
            using ArgsTuple = std::tuple<Args...>;
    
            using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
    
            using CurryType = currying<FType, std::tuple<GivenArgs..., Args...>, RestArgsPrime>;
    
            return CurryType{ std::move(func), std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)... };
        }
    
        template <typename... Args, size_t... Indices>
        constexpr
        auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) const& {
            return func(std::get<Indices>(given_args)..., std::forward<Args>(args)...);
        }
    
        template <typename... Args, size_t... Indices>
        constexpr
        auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) && {
            return func(std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)...);
        }
    };
    
    // ---
    
    template <typename FType, size_t... Indices>
    constexpr
    auto curry(FType&& func, std::index_sequence<Indices...>) {
        using RealFType = typename function_type<FType>::type;
        using FTypeTraits = function_traits<RealFType>;
    
        using CurryType = currying<FType, std::tuple<>, std::tuple<typename FTypeTraits::template arg_type<Indices>...>>;
    
        return CurryType{ std::move(func) };
    }
    
    template <typename FType>
    constexpr
    auto curry(FType&& func) {
        using RealFType = typename function_type<FType>::type;
        using FTypeArity = typename function_traits<RealFType>::arity;
    
        return curry(std::move(func), std::make_index_sequence<FTypeArity::value>{});
    }
    
    // ---
    
    int main() {
        auto add = curry([](int a, int b) { return a + b; });
    
        std::cout << add(5)(10) << std::endl;
    }
    
    0 讨论(0)
  • 2020-11-28 04:19

    If you're using C++14 it's very easy:

    template<typename Function, typename... Arguments>
    auto curry(Function function, Arguments... args) {
        return [=](auto... rest) {
            return function(args..., rest...);
        }; // don't forget semicolumn
    }
    

    You can then use it like this:

    auto add = [](auto x, auto y) { return x + y; }
    
    // curry 4 into add
    auto add4 = curry(add, 4);
    
    add4(6); // 10
    
    0 讨论(0)
  • 2020-11-28 04:24

    Some great answers here. I thought I would add my own because it was fun to play around with the concept.

    Partial function application: The process of "binding" a function with only some of its parameters, deferring the rest to be filled in later. The result is another function with fewer parameters.

    Currying: Is a special form of partial function application where you can only "bind" a single argument at a time. The result is another function with exactly 1 fewer parameter.

    The code I'm about to present is partial function application from which currying is possible, but not the only possibility. It offers a few benefits over the above currying implementations (mainly because it's partial function application and not currying, heh).

    • Applying over an empty function:

      auto sum0 = [](){return 0;};
      std::cout << partial_apply(sum0)() << std::endl;
      
    • Applying multiple arguments at a time:

      auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;};
      std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10
      
    • constexpr support that allows for compile-time static_assert:

      static_assert(partial_apply(sum0)() == 0);
      
    • A useful error message if you accidentally go too far in providing arguments:

      auto sum1 = [](int x){ return x;};
      partial_apply(sum1)(1)(1);
      

      error: static_assert failed "Attempting to apply too many arguments!"

    Other answers above return lambdas that bind an argument and then return further lambdas. This approach wraps that essential functionality into a callable object. Definitions for operator() allow the internal lambda to be called. Variadic templates allow us to check for someone going too far, and an implicit conversion function to the result type of the function call allows us to print the result or compare the object to a primitive.

    Code:

    namespace detail{
    template<class F>
    using is_zero_callable = decltype(std::declval<F>()());
    
    template<class F>
    constexpr bool is_zero_callable_v = std::experimental::is_detected_v<is_zero_callable, F>;
    }
    
    template<class F>
    struct partial_apply_t
    {
        template<class... Args>
        constexpr auto operator()(Args... args)
        {
            static_assert(sizeof...(args) == 0 || !is_zero_callable, "Attempting to apply too many arguments!");
            auto bind_some = [=](auto... rest) -> decltype(myFun(args..., rest...))
            {
               return myFun(args..., rest...);
            };
            using bind_t = decltype(bind_some);
    
            return partial_apply_t<bind_t>{bind_some};
        }
        explicit constexpr partial_apply_t(F fun) : myFun(fun){}
    
        constexpr operator auto()
        {
            if constexpr (is_zero_callable)
                return myFun();
            else
                return *this; // a callable
        }
        static constexpr bool is_zero_callable = detail::is_zero_callable_v<F>;
        F myFun;
    };
    

    Live Demo

    A few more notes:

    • I chose to use is_detected mainly for enjoyment and practice; it serves the same as a normal type trait would here.
    • There could definitely be more work done to support perfect forwarding for performance reasons
    • The code is C++17 because it requires for constexpr lambda support in C++17
      • And it seems that GCC 7.0.1 is not quite there yet, either, so I used Clang 5.0.0

    Some tests:

    auto sum0 = [](){return 0;};
    auto sum1 = [](int x){ return x;};
    auto sum2 = [](int x, int y){ return x + y;};
    auto sum3 = [](int x, int y, int z){ return x + y + z; };
    auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;};
    
    std::cout << partial_apply(sum0)() << std::endl; //0
    static_assert(partial_apply(sum0)() == 0, "sum0 should return 0");
    std::cout << partial_apply(sum1)(1) << std::endl; // 1
    std::cout << partial_apply(sum2)(1)(1) << std::endl; // 2
    std::cout << partial_apply(sum3)(1)(1)(1) << std::endl; // 3
    static_assert(partial_apply(sum3)(1)(1)(1) == 3, "sum3 should return 3");
    std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10
    //partial_apply(sum1)(1)(1); // fails static assert
    auto partiallyApplied = partial_apply(sum3)(1)(1);
    std::function<int(int)> finish_applying = partiallyApplied;
    std::cout << std::boolalpha << (finish_applying(1) == 3) << std::endl; // true
    
    auto plus2 = partial_apply(sum3)(1)(1);
    std::cout << std::boolalpha << (plus2(1) == 3) << std::endl; // true
    std::cout << std::boolalpha << (plus2(3) == 5) << std::endl; // true
    
    0 讨论(0)
  • 2020-11-28 04:27

    These Links are relevant:

    The Lambda Calculus page on Wikipedia has a clear example of currying
    http://en.wikipedia.org/wiki/Lambda_calculus#Motivation

    This paper treats currying in C/C++
    http://asg.unige.ch/site/papers/Dami91a.pdf

    0 讨论(0)
提交回复
热议问题