3
votes

To keep things generic and straightforward, say that I have a std::vector of integers, such as:

std::vector<int> v;

Now, what I am wondering is, is it possible to take n (where n is a constant known at compile time) values from v and pass them to an arbitrary function? I know that this is doable with variadic templates:

template<typename... T>
void pass(void (*func)(int, int, int), T... t) {
  func(t...);
}

And then we hope 'pass' is called with exactly 3 integers. The details don't matter so much. What I am wondering is, is the following somehow doable:

void pass(void (*func)(int, int, int), std::vector<int> &t) {
  auto iter = t.begin();
  func((*iter++)...);
}

Where ... is being used like a variadic template? Essentially, I'm asking if I can

  1. Expand a std::vector or other STL container into a variadic template with n elements
  2. And/or in-order pass these values directly to a function being called

Is this possible with C++11? Noting that I need this to work on MSVC v120/VS2013.

1
You can do it with a tuple (and its a pretty slick trick too), but a vector.. not that I've ever seen. Encoding a function call is a compile-time thing; vector content, even via initialization list, is a runtime entity. It begs the question, however, why not just pass the vector ? - WhozCraig
Thanks for answering WhozCrag. This is meant to work in a generic setting (ie, inside a reflection system with reflected function invocation). My approach is to pull a parameter stack and encode their addresses in a vector, pass the vector to the actual compiler-generated caller function, then unpack the vector and pass the n-parameters to the function during invocation. I will take a look at the tuple approach. Thanks! - Ben H
Given your description a doubt the tuple approach will be sufficient. Knowing all the params are by-address may be enough for a platform-dependent approach, such as a simple asm proc that takes a base-array pointer of param pointers and a size, a function pointer, and goes to town on it. Type safety (knowing the function you're calling actually expects n-params) goes out the window in all of this in case it wasn't obvious. - WhozCraig
A simple approach oft used in C++03 is to overload pass for a reasonable range of numbers of func arguments - can even be done with the preprocessor. Plenty of examples of this kind of thing in Alexandrescu's Loki library. It does mean you have some arbitrary limit on the number of arguments you support, after which you get a compilation error. - Tony Delroy
Tony D, that's how I implemented it in my last approach. I wanted to try doing this for n-parameters using variadic templates. @WhozCraig, I'm not locked-in on using a vector. I just switched everything around to using a tuple and it seems to work great. However, I am passing by copy right now so I am going to try and see if I can't convert the parameter packs into new packs with pointer types instead. That ought to be an interesting challenge. Thanks for the help. - Ben H

1 Answers

7
votes

It's definitely possible, but you cannot determine the safety of doing it at compile time. This is, as WhozCraig says, because the vector lacks a compile-time size.

I'm still trying to earn my template meta programming wings, so I may have done things a little unusually. But the core idea here is to have a function template recursively invoke itself with the next item in the vector until it has built up a parameter pack with the desired parameters. Once it has that, it's easy to pass it to the function in question.

The implementation of the core here is in apply_first_n, which accepts a target std::function<R(Ps...)>, and a vector, and a parameter pack of Ts.... When Ts... is shorter than Ps... it builds up the pack; once it's the same size, it passes it to the function.

template <typename R, typename... Ps, typename... Ts>
typename std::enable_if<sizeof...(Ps) == sizeof...(Ts), R>::type
apply_first_n(std::function<R(Ps...)> f, const std::vector<int> &v, Ts&&... ts)
{
    if (sizeof...(Ts) > v.size())
        throw std::out_of_range("vector too small for function");
    return f(std::forward<Ts>(ts)...);
}

template <typename R, typename... Ps, typename... Ts>
typename std::enable_if<sizeof...(Ps) != sizeof...(Ts), R>::type
apply_first_n(std::function<R(Ps...)> f, const std::vector<int> &v, Ts&&... ts)
{
    const int index = sizeof...(Ps) - sizeof...(Ts) - 1;
    static_assert(index >= 0, "incompatible function parameters");
    return apply_first_n(f, v, *(std::begin(v) + index), std::forward<Ts>(ts)...);
}

You call this with, e.g., apply_first_n(std::function<int(int, int)>(f), v);. In the live example, make_fn just makes the conversion to std::function easier, and ProcessInts is a convenient testing function.

I'd love to figure out how to avoid the use of std::function, and to repair any other gross inefficiencies that exist. But I'd say this is proof that it's possible.


For reference, I took the above approach further, handling set, vector, tuple, and initializer_list, as well as others that match the right interfaces. Removing std::function seemed to require the func_info traits class, as well as several overloads. So while this extended live example is definitely more general, I'm not sure I'd call it better.