6

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;
}
Samuel
  • 378
  • 1
  • 9
  • Do you mean "recursively" or *iteratively*? Because those aren't the same word, and have vastly different meanings, and the code you posted uses iteration rather than recursion. Indeed, using actual recursion would probably be a lot easier. – Nicol Bolas Sep 22 '19 at 14:36
  • Also, is `n` compile-time-defined or runtime defined? Because the latter isn't really going to be possible (or at the very least, not easily possible). – Nicol Bolas Sep 22 '19 at 14:41
  • possible duplicate of https://stackoverflow.com/questions/28522556/issues-applying-stdbind-recursively-on-a-stdfunction – The Philomath Sep 22 '19 at 14:50
  • I meant iteratively, but a recursive solution would also be accepted. N is compile time defined. – Samuel Sep 22 '19 at 15:05

2 Answers2

6

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.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • 1
    That's exactly what I want, but sadly index_sequence is c++14 only. Is there a c++11 alternative? – Samuel Sep 22 '19 at 15:01
  • I tried implementing index_sequence but I got the following ```error: ‘integer_sequence’ in namespace ‘std’ does not name a template type```The implementation is ```template using index_sequence = std::integer_sequence;``` – Samuel Sep 22 '19 at 15:39
  • @Samuel `std::integer_sequence` is no less C++14 than `std::index_sequence`... Have you followed the link? There's a `struct IndexSequence`, a helper struct and a final `using` for the helper template (`make ...`). Whichever names you decide to choose yourself, make sure you use them consistently. And be aware that you are not allowed to add new functions or classes to `std` namespace (apart from specialisations of existing ones). – Aconcagua Sep 22 '19 at 15:45
2

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;
}
Philipp Lenk
  • 921
  • 6
  • 14
  • 1
    Your variant doesn't cover `make_index_sequence<0>`. It *could* do so (producing empty sequence then), if you shift subtruction of one into the detail (you'd need to skip initial 0 in stopping specialisation then, too). Compare my link provided. – Aconcagua Sep 22 '19 at 15:59
  • You are, of course, absolutely correct. Thanks ;-) (adjusted in edit) – Philipp Lenk Sep 22 '19 at 16:03
  • Thanks for the answer! The step by step helped a lot. – Samuel Sep 22 '19 at 16:09
  • One question, could your answer also work with tuples in c++11? – Samuel Sep 22 '19 at 16:11
  • Well, you would have to replace the indexing with std::get and use something like tuple_size to know which index sequence to create. But those minor details aside, yes it should work – Philipp Lenk Sep 22 '19 at 16:13