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?

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

15 Comments

Why are you forwarding func at the call site?
@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
Now that's subtle. Mind showing an example where it matters? I can't get it to work. (+1 btw)
@jrok: That's because only Clang supports references to this AFAIK. Here is an example
@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
|
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() );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.