11

I am trying to learn about variadic templates in C++11. I have a class which is basically a wrapper around a std::array. I want to be able to pass function objects (ideally lambdas) to a member function and then have the elements of the std::array passed on as parameters of the function object.

I have used a static_assert to check that the number of parameters matches the length of the array but I cannot think of a way to pass the elements as arguments.

Here is the code

#include <iostream>
#include <array>
#include <memory>
#include <initializer_list>

using namespace std;

template<int N, typename T>
struct Container {
    template<typename... Ts>
    Container(Ts&&... vs) : data{{std::forward<Ts>(vs)...}} {
        static_assert(sizeof...(Ts)==N,"Not enough args supplied!");
    }

    template< typename... Ts>
    void doOperation( std::function<void(Ts...)>&& func )
    {
        static_assert(sizeof...(Ts)==N,"Size of variadic template args does not match array length");

        // how can one call func with the entries
        // of data as the parameters (in a way generic with N)
    }

    std::array<T,N> data;
};

int main(void)
{
    Container<3,int> cont(1,2,3);

    double sum = 0.0;
    auto func = [&sum](int x, int y, int z)->void{
        sum += x;
        sum += y;
        sum += z;
    };

    cont.doOperation(std::function<void(int,int,int)>(func));

    cout << sum << endl;

    return 0;
}

So my question (as indicated in the code) is how can one pass the entries of data onto the function func in a way which is generic with N?

Bonus Question: Is it possible to do away with the unsightly conversion to std::function in main and pass in a lambda directly?

Dan
  • 12,857
  • 7
  • 40
  • 57

2 Answers2

15

Given the well-known indices infrastructure:

namespace detail
{
    template<int... Is>
    struct seq { };

    template<int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };

    template<int... Is>
    struct gen_seq<0, Is...> : seq<Is...> { };
}

You could redefine your class template this way:

template<int N, typename T>
struct Container {
    template<typename... Ts>
    Container(Ts&&... vs) : data{{std::forward<Ts>(vs)...}} {
        static_assert(sizeof...(Ts)==N,"Not enough args supplied!");
    }

    template<typename F>
    void doOperation(F&& func)
    {
        doOperation(std::forward<F>(func), detail::gen_seq<N>());
    }

    template<typename F, int... Is>
    void doOperation(F&& func, detail::seq<Is...>)
    {
        (std::forward<F>(func))(data[Is]...);
    }

    std::array<T,N> data;
};

Here is a live example.

Notice, that you do not need to construct an std::function object in main(): the std::function can be implicitly constructed from the lambda. However, you do not even need to use std::function at all here, possibly incurring in an unnecessary run-time overhead.

In the solution above, I just let the type of the callable object to be a template parameter that can be deduced by the compiler.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Why are you forwarding `func` at the call site? – jrok May 30 '13 at 11:39
  • 3
    @jrok: Well, it's mostly a theoretical thing. The callable object could be an rvalue, and it could have an `operator () &&`. Very unlikely, but still possible – Andy Prowl May 30 '13 at 11:41
  • Now that's subtle. Mind showing an example where it matters? [I can't get it to work.](http://coliru.stacked-crooked.com/view?id=f65a8b7e2330253abae7aab3b75f0fb1-7063104e283ed82d51a6fde7370c6e59) (+1 btw) – jrok May 30 '13 at 11:54
  • @jrok: That's because only Clang supports references to `this` AFAIK. [Here](http://coliru.stacked-crooked.com/view?id=89a241581f6247cbc7a061dbd77466f8-0be4386eb9b168939a6a7d681c02387e) is an example – Andy Prowl May 30 '13 at 11:59
  • I tried having the callable object as a template whose type would be determined but I couldn't seem to get it not to complain. – Dan May 30 '13 at 12:07
  • @Dan: What do you mean? What error message are you getting? The live example I provided shows it should compile – Andy Prowl May 30 '13 at 12:08
  • Indeed your example does compile. The syntax I was trying to use was `template void doOp(F&& func(Ts...))` but it wasn't happy. – Dan May 30 '13 at 12:10
  • Is there an equivalent of the indices that is part of `std`? Also do you know of a link explaining how it works? Thanks for the answer, btw. – Dan May 30 '13 at 12:13
  • @Dan: Oh, I see. No, that's indeed illegal syntax. But you can figure out some type traits to retrieve the number of arguments of the callable object anyway – Andy Prowl May 30 '13 at 12:13
  • @Dan: Not in C++11, but there will be in C++14. No, I don't know of any explanatory link, but you can ask on SO ;) – Andy Prowl May 30 '13 at 12:13
  • @Dan: jrok probably refers to the link I provided in the comments, not the one in the answer – Andy Prowl May 30 '13 at 12:14
  • @Dan, yes, I'm talking about ref-qualifiers for `this`. – jrok May 30 '13 at 12:15
  • @AndyProwl I'm still struggling to understand *exactly* how this works. In the second call to `doOperation` the second parameter `detail::seq` is a type, correct? How can it be that this parameter doesn't have a name? And in principle could I do something like `data[Is]... = other_data[Is]...`? – Dan May 30 '13 at 12:54
  • 1
    @Dan: It is a type, yes, and not all parameters in a function need to have a name (as long as you don't use them). In this case, I do not need to use that parameter inside the function: the parameter is just there to allow deducing `Is` during type deduction, so that I can then create a pattern based on it (like `data[Is]`) and expand it. Concerning your example, no you can't do that, pack expansion can be used only in some contexts and that is not a legal one – Andy Prowl May 30 '13 at 12:58
  • @Dan C++14 version can be found at http://en.cppreference.com/w/cpp/utility/integer_sequence – sluki May 27 '16 at 15:57
2

You can use this utility template to create a sequence of indices at compile time:

template< std::size_t... Ns >
struct indices {
    typedef indices< Ns..., sizeof...( Ns ) > next;
};

template< std::size_t N >
struct make_indices {
    typedef typename make_indices< N - 1 >::type::next type;
};

template<>
struct make_indices< 0 > {
    typedef indices<> type;
};

Then make a caller function that takes indices as a parameter so you got a way of deducing the indices sequence:

template<typename... Ts, size_t...Is>
void call(std::function<void(Ts...)>&& func, indices<Is...>)
{
    func( data[Is]... );
}

And then you can call it like this:

template< typename... Ts>
void doOperation( std::function<void(Ts...)>&& func )
{
    static_assert(sizeof...(Ts)==N,"Size of variadic template args does not match array length");
    call( std::forward<std::function<void(Ts...)>>(func), typename  make_indices<N>::type() );
}
jrok
  • 54,456
  • 9
  • 109
  • 141