3

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.

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
Ben H
  • 31
  • 5
  • 3
    You can do it with a tuple [(and its a pretty slick trick too)](http://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971), 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 May 30 '14 at 03:29
  • 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 May 30 '14 at 03:36
  • 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 May 30 '14 at 03:45
  • 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 May 30 '14 at 04:05
  • 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 May 30 '14 at 04:25
  • It's now working with pointers using a static array of void * instead of tuple. When instantiating the function I use a static_cast to a pointer to the variadic parameter that's being expanded in the caller function and then dereference that in the actual call expansion code. – Ben H May 30 '14 at 05:08

1 Answers1

7

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.

Michael Urman
  • 15,737
  • 2
  • 28
  • 44
  • That is quite cool, and definitely looks like it would work. The only thing I want to note is my application requires storing different types, which is why I ultimately couldn't go with a vector. A tuple makes more sense, but I ended up leaning toward a static array of void pointers, allowing me to simply reference the parameters being passed rather than copy them (again, this is strongly due to how my application is structured). Thank you for the detailed and working example. You remind me of how much more I need to learn of variadic templates. – Ben H Jun 02 '14 at 03:06
  • @BenH It sounds like for your case the tuple unpacking trick definitely has a lot going for it. In C++ I always prefer type-safety when it's available. Thanks for the interesting question though! – Michael Urman Jun 02 '14 at 11:56