Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind parameters recursively to a function?

Tags:

c++

c++11

I need to take an array of input arguments of size n and bind its values to another function that takes n arguments.

I tried using bind to pass the elements of the array one by one to the function but it didn't work (kind of like using bind_front inside a loop).

What I need is something like:

#include <iostream>
#include <functional>


using namespace std;

int addThreeNumbers(int a, int b, int c)
{
    return a+b+c;
}

int main()
{
    int parameters[] = {1, 2, 3};

    auto f = addThreeNumbers;

    // Bind each element from the list one by one
    for(int i=1; i<parametersLength; i++)
    {
         f = bind(f, parameters[i]);
    }

    f(); // call addThreeNumbers

    return 0;
}

The solution would need to work with parameters being an array of any size. Is this possible to do using c++ 11?

Edit

I got it working thanks to Aconcagua and Philipp Lenk! Here is the working code:

#include <iostream>
#include <functional>


using namespace std;

// index sequence only
template <size_t ...>
struct indexSequence
 { };

template <size_t N, size_t ... Next>
struct indexSequenceHelper : public indexSequenceHelper<N-1U, N-1U, Next...>
 { };

template <size_t ... Next>
struct indexSequenceHelper<0U, Next ... >
 { using type = indexSequence<Next ... >; };

template <size_t N>
using makeIndexSequence = typename indexSequenceHelper<N>::type;

int addThreeNumbers(int a, int b, int c)
{
    return a+b+c;
}


template <typename F, typename T, size_t N, size_t ... I>
auto dispatch(F function, T(&array)[N], indexSequence<I ...>) -> decltype(function(array[I]...))
{
    return function(array[I]...);
}

template <typename F, typename T, size_t N>
auto dispatch(F function, T(&array)[N]) -> decltype(dispatch(function, array, makeIndexSequence<N>()))
{
    return dispatch(function, array, makeIndexSequence<N>());
}


int main()
{
    int a[] = { 1, 2, 3 };
    int s = dispatch(addThreeNumbers, a);

    cout << s << endl;

    return 0;
} 

Edit2

Got it working using tuples as well:

#include <iostream>
#include <tuple>
#include <string>


using namespace std;


// index sequence only
template <size_t ...>
struct indexSequence
 { };

template <size_t N, size_t ... Next>
struct indexSequenceHelper : public indexSequenceHelper<N-1U, N-1U, Next...>
 { };

template <size_t ... Next>
struct indexSequenceHelper<0U, Next ... >
 { using type = indexSequence<Next ... >; };

template <size_t N>
using makeIndexSequence = typename indexSequenceHelper<N>::type;


template <class F, class Tuple, std::size_t... Is>
constexpr auto apply_impl(const F& f, Tuple t, indexSequence<Is...>) -> decltype(f(std::get<Is>(t)...))
{
    return f(std::get<Is>(t)...);
}

template <class F, class Tuple>
constexpr auto apply(const F& f, Tuple t) -> decltype(apply_impl(f, t, makeIndexSequence<std::tuple_size<Tuple>{}>{}))
{
    return apply_impl(f, t, makeIndexSequence<std::tuple_size<Tuple>{}>{});
}


int sum(int a, int b, string c)
{
    cout << c << endl;
    return a+b;
}


int main()
{
    auto parameters = std::make_tuple(1,2,"1+2=3");

    int s = apply(sum, parameters);

    cout << s << endl;

    return 0;
}
like image 275
Samuel Avatar asked Sep 22 '19 14:09

Samuel


2 Answers

If I read your question right, you actually intend to decompose the array into single parameters. If so, you can do that with a std::index_sequence:

template <typename F, typename T, size_t N, std::size_t ... I>
auto dispatch(F function, T(&array)[N], std::index_sequence<I ...>)
{
    return function(array[I]...);
}

template <typename F, typename T, size_t N>
auto dispatch(F function, T(&array)[N])
{
    return dispatch(function, array, std::make_index_sequence<N>());
}

Usage example:

int sum(int a, int b, int c)
{
    return a + b + c;
}

int a[] = { 1, 2, 3 };
int s = dispatch(sum, a);

Of course, you'll get a compilation error if array length and number of function parameters do not match...

Edit: As std::index_sequence is not available before C++14, you might implement your own variant of. This answer explains how you could do so.

like image 93
Aconcagua Avatar answered Oct 05 '22 23:10

Aconcagua


Basically you seem to be looking for std::apply (or maybe a lambda wrapping a call to it + the arguments)

If your question concerned C++17 or newer and your parameters were stored in a std::array instead, the solution would look quite simple:

#include <array>
#include <iostream>
#include <tuple>

int addThreeNumbers(int a, int b, int c)
{
    return a+b+c;
}

int main()
{
    std::array parameters = {1, 2, 3};

    auto f = [&]()
    {
        return std::apply(addThreeNumbers,parameters);
    };

    std::cout<<f();

    return 0;
}

As you asked for C++11 your life is going to be ever so slightly more difficult, as std::apply is not yet part of the standard library. Something similar can, however, be implemented, the linked cppreference page even contains an implementation that can be used pretty much as shown: (significantly simplified for your case, using const references instead of proper perfect forwarding to be more concise and clear about the intentions and method used, without std::invoke(another C++17 addition) and only for array, to avoid tuple_size(which was not defined for std::array before C++17 either))

#include <iostream>
#include <tuple>

int addThreeNumbers(int a, int b, int c)
{
    return a+b+c;
}

namespace detail
{
    template <class F, class T, std::size_t N, std::size_t... I>
    constexpr decltype(auto) apply_impl(const F& f, const std::array<T,N>& t, std::index_sequence<I...>)
    {
        return f(t[I]...);
    }
} 

template <class F, class T, std::size_t N>
constexpr decltype(auto) apply(const F& f, const std::array<T,N>& t)
{
    return detail::apply_impl(f, t, std::make_index_sequence<N>{});
}

int main()
{
    std::array<int,3> parameters= {1, 2, 3};

    auto f = [&]()
    {
        return apply(addThreeNumbers,parameters);
    };

    std::cout<<f();

    return 0;
}

Cool, we are down to C++14 now, yet trying to compile this as C++11 reveals yet another problem: std::index_sequence was a C++14 addition. Luckily for us this, too, was not a language addition and can be implemented for C++11(again, simplified and grossly inefficient version, the answer linked by Aconcagua contains a much better and detailed explanation):

template <std::size_t... I>
struct index_sequence{};

namespace detail
{
    template <std::size_t N, std::size_t... I>
    struct index_sequence_helper
    {
        using type=typename index_sequence_helper<N-1,N-1,I...>::type;
    };

    template <std::size_t... I>
    struct index_sequence_helper<0,I...>
    {
        using type=index_sequence<I...>;
    };
}

template <std::size_t N>
using make_index_sequence=typename detail::index_sequence_helper<N>::type;

With this in place, we finally have a complete and working C++11 solution for your original question(with some more minor adjustments, like missing return type deduction):

#include <array>
#include <iostream>

int addThreeNumbers(int a, int b, int c)
{
    return a+b+c;
}

template <std::size_t... I>
struct index_sequence{};

namespace detail
{
    template <std::size_t N, std::size_t... I>
    struct index_sequence_helper
    {
        using type=typename index_sequence_helper<N-1,N-1,I...>::type;
    };

    template <std::size_t... I>
    struct index_sequence_helper<0,I...>
    {
        using type=index_sequence<I...>;
    };
}

template <std::size_t N>
using make_index_sequence=typename detail::index_sequence_helper<N>::type;

namespace detail {
    template <class F, class T, std::size_t N, std::size_t... I>
    constexpr auto apply_impl(const F& f, const std::array<T,N>& t, index_sequence<I...>) -> decltype(f(t[I]...))
    {
        return f(t[I]...);
    }
}

template <class F, class T, std::size_t N>
constexpr auto apply(const F& f, const std::array<T,N>& t) -> decltype(detail::apply_impl(f, t, make_index_sequence<N>{}))
{
    return detail::apply_impl(f, t, make_index_sequence<N>{});
}

int main()
{
    std::array<int,3> parameters= {1, 2, 3};

    auto f = [&]()
    {
        return apply(addThreeNumbers,parameters);
    };

    std::cout<<f();

    return 0;
}
like image 38
Philipp Lenk Avatar answered Oct 05 '22 23:10

Philipp Lenk