1

I have to write a template function with the following interface:

template <typename... Args>
void create(Args&&... args)

I need to be able to call it like this:

create([](int, float){}, 42, 5.f);

and I want the function (lambda) to be called with the 2 arguments passed after it. How can I achieve this?

I'm constrained with the interface of create() - I cannot separate the function parameter ouf of the parameter pack because of... reasons (just trust me). Perhaps it can be extracted with some tricks like in here and have the rest of the parameter pack forwarded to it...?

This should work with functions taking an arbitrary number of arguments - not just an int and a float.

onqtam
  • 4,356
  • 2
  • 28
  • 50
  • 6
    "I cannot separate the function parameter out of the parameter pack because of... reasons" Those reasons better be *very* good, because it really does not make any sense to me why you would constrain yourself like this. – Rakete1111 Jul 12 '17 at 16:44
  • 4
    Can you please elaborate on those reasons? Otherwise `template void create(First&& first, Args&&... args)` is how you would normally do it. – NathanOliver Jul 12 '17 at 16:44
  • My "reasons" are that I'm reusing a big template machinery and I'm trying to extend it without rewriting it. It is a generic resource manager template class which inherits from a "creator" class which has the ```create()``` method and to it are passed a ```std::string``` (which I have omitted in my question) and a parameter pack. The idea is that a resource manager for a specific type needs the same arguments passed to ```create```, but I want to extend it so ```create``` can behave differently - with different arguments. Not sure if this explains it... – onqtam Jul 12 '17 at 16:48
  • 1
    That doesn't really explain why you can't change the template parameter list - it shouldn't have to match anything else if it just hides a base class template method. – Useless Jul 12 '17 at 16:51
  • 2
    You can call, from `create()`, a `create2(F func, Args ... args)`? – max66 Jul 12 '17 at 16:52
  • 1
    Actually here is a [link to my code](https://github.com/onqtam/game/blob/3cfa652e11d20450d28b8b4701715bcdcd7356b5/src/core/GraphicsHelpers.h#L74) - the idea is that ```createCube()``` takes 0 arguments, but ```createGrid()``` may take 5 arguments. I was going to do runtime dispatch based on the string passed to ```create()``` but then it wouldn't compile if I pass a few arguments to ```create()``` because ```createCube()``` takes 0. So my idea is to pass the creator function as well and not do such runtime-string-dispatching. Hope that helps clear things a bit. – onqtam Jul 12 '17 at 16:52
  • Do your compiler support the upcoming C++17 standard? Then perhaps some creative overloading using [`std::enable_if`](http://en.cppreference.com/w/cpp/types/enable_if) together with [`std::is_invocable`](http://en.cppreference.com/w/cpp/types/is_invocable) for some SFINAE? – Some programmer dude Jul 12 '17 at 16:52
  • I'm using VS 2017 with ```/std:c++latest``` and gcc/clang with ```-std=c++1z``` – onqtam Jul 12 '17 at 16:53
  • I'll try what @max66 suggests!!! – onqtam Jul 12 '17 at 16:54
  • Well... my was a question; as suggestion is too obvious. I suppose the answer is "yes". – max66 Jul 12 '17 at 16:56
  • @max66 well it worked - this *too obvious* thing was exactly what I needed - thanks! Put it as an answer and I shall accept it. So after all my question had a simple answer and there was no need for downvotes.... oh well :) – onqtam Jul 12 '17 at 17:01
  • "may take 5 arguments. I was going to do runtime dispatch based on the string passed to create() but then it wouldn't compile if I pass a few arguments to create() because createCube() takes 0. So my idea is to pass the creator function as well and not do such runtime-string-dispatching." You seem to think that not having a separete arg from the pack will prevent compile time errors. Your problem is, I cannot think of a situation where `template void create` would break while `template void create` that invokes the first arg won't. Please clarify. – Yakk - Adam Nevraumont Jul 12 '17 at 18:12
  • Possible duplicate of [In c++ 11, how to invoke an arbitrary callable object?](https://stackoverflow.com/questions/32918679/in-c-11-how-to-invoke-an-arbitrary-callable-object) – Justin Jul 12 '17 at 18:51

2 Answers2

3

A simple solution is demand the execution to an helper function without signature limitations.

By example

#include <iostream>

template <typename L, typename ... Args>
void create_helper (L const & l, Args && ... args)
 { (void)l(args...); }

template <typename ... Args>
void create (Args && ... args)
 { create_helper(args...); }

int main()
 {
   create([](int i, float f){ std::cout << i << ", " << f << std::endl; },
          42, 5.f);
 }
max66
  • 65,235
  • 10
  • 71
  • 111
2

This should work with functions taking an arbitrary number of arguments - not just an int and a float.

In C++14 you can unpack them by means of a generic lambda invoked in-place and to which you forward your arguments.
As a minimal, working example:

#include<utility>

template <typename... Args>
void create(Args&&... args) {
    [](auto &&f, auto&&... p){
        std::forward<decltype(f)>(f)(std::forward<decltype(p)>(p)...);
    }(std::forward<Args>(args)...);
}

int main() {
    create([](int, float){}, 42, 5.f);
}

In C++11 you can use another function template or an equivalent functor that does the same job, then use them instead of the generic lambda.
As a minimal, working example:

#include<utility>

struct Functor {
    template<typename F, typename... Args>
    static void invoke(F &&f, Args&&... args) {
        std::forward<F>(f)(std::forward<Args>(args)...);
    }
};

template <typename... Args>
void create(Args&&... args) {
    Functor::invoke(std::forward<Args>(args)...);
}

int main() {
    create([](int, float){}, 42, 5.f);
}
skypjack
  • 49,335
  • 19
  • 95
  • 187