3

I have the following template function:

template <typename...Args, typename Func>
void call(const char *name, Args...args, Func f)
{
        f(3);
}

When I try to use it, like

    call("test", 1, 2, 3, [=](int i) { std::cout<< i; });

The compiler complains that it cannot infer the template argument Func. How can this problem be solved, knowing that args can be any type except a function pointer.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Kurt Pattyn
  • 2,758
  • 2
  • 30
  • 42
  • 1
    Can you reorder arguments? – zch Apr 12 '13 at 17:47
  • The name argument is a method, and the args are arguments to the method, the func is a callback function. So, the order seems 'natural' in this way. So, I'd rather not change the order of the arguments. – Kurt Pattyn Apr 12 '13 at 17:49
  • 1
    Otherwise, the language rule is that the parameter pack has to be the last argument. – Bo Persson Apr 12 '13 at 17:55

3 Answers3

8

From 14.1p11:

A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument (14.8.2).

If you want to keep the callable as the last argument, you can use forward_as_tuple:

template <typename...Args, typename Func>
void call(const char *name, std::tuple<Args...> args, Func f)
{
        f(3);
}

call("test", std::forward_as_tuple(1, 2, 3), [=](int i) { std::cout<< i; });

We can actually do better by synthesizing the tuple to contain the callable as well:

#include <tuple>

template<typename... Args_F>
void call_impl(const char *name, std::tuple<Args_F... &&> args_f) {
   auto &&f = std::get<sizeof...(Args_F) - 1>(args_f);
   f(3);
}

template<typename...ArgsF>
void call(const char *name, ArgsF &&...args_f) {
   call_impl(name, std::tuple<ArgsF &&...>(std::forward<ArgsF>(args_f)...));
}
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • This is also a great solution. I definitely want to hide the std::tuple from the outside, so the synthesized solution is nice. +1! – Kurt Pattyn Apr 13 '13 at 07:30
4

Write get_last, which extracts the last element of a parameter pack.

Call it f. Call f.

As an example,

template<typename T0>
auto get_last( T0&& t0 )->decltype(std::forward<T0>(t0))
{
  return std::forward<T0>(t0);
}
template<typename T0, typename... Ts>
auto get_last( T0&& t0, Ts&&... ts )->decltype(get_last(std::forward<Ts>(ts)...))
{
  return get_last(std::forward<Ts>(ts)...);
}

if you don't care about overload resolution, just calling get_last and treating it like a functor might be enough:

template <typename...Args>
void call(const char *name, Args...&& args)
{
    auto&& f = get_last(std::forward<Args>(args)...);
    f(3);
}

The next step up would be to do some SFINAE enable_if magic in order to make the call fail to match if you don't pass a valid functor last: however, this is probably overkill.

To detect if the f(3) will work, a simple traits class:

// trivial traits class:
template<typename T>
struct is_type:std::true_type {};

template<typename Functor, typename=void>
struct can_be_called_with_3:std::false_type {}

template<typename Functor>
struct can_be_called_with_3<Functor,
  typename std::enable_if<
    std::is_type< decltype(
      std::declval<Functor>(3)
    ) >::value
  >::type
>:std::true_type {}

which is pretty silly. A fancier traits class would have to be used if your requirements for the passed in type are more complex (say, you want it to be called with the arguments).

Then you augment call with:

template <typename...Args>
auto call(const char *name, Args...&& args)
  -> typename std::enable_if<
       can_be_called_with_3< decltype( get_last(std::forward<Args>(args)... ) ) >::value
     >::type
{ /* body unchanged */ }

which is pretty obtuse.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Can you elaborate on this with an example? Given the fact that I do not know the type of the parameters beforehand. – Kurt Pattyn Apr 12 '13 at 17:52
  • Do you need `last_type` or `type_is_functor_that_takes_these_arguments` traits classes? (Ie, do you care if failure to pass in a functor gives a "no valid function found" or a template instantiation error?) – Yakk - Adam Nevraumont Apr 12 '13 at 17:56
  • Won't the call to `f(3)` generate an error if it is not a functor? – Kurt Pattyn Apr 12 '13 at 17:59
  • Yes. But that error will happen after the template is instantiated. It is possible (but more complex) to actually require that the last argument of `Args&&... args` is valid functor, and if not make the call to `call` fail to match the overload. I suspect this is overkill in your case, and it only really useful if you have another `call` function that doesn't take the functor as the last argument (say, you wanted a default functor, so the caller doesn't always have to pass one in). – Yakk - Adam Nevraumont Apr 12 '13 at 18:01
  • Your answer is great! The check is not really necessary, but could come in handy, in case a `void` method is called. So, if you could give the example as well it would be nice! – Kurt Pattyn Apr 12 '13 at 18:03
  • @RobertMason How do you use `static_assert` to cause SFINAE? – Yakk - Adam Nevraumont Apr 12 '13 at 18:22
  • 1
    Well, it doesn't appear like the OP needs overloading. Therefore, the only purpose of SFINAE is to cause an error saying the function call is not found (and then, with GCC, it'll spew notes about each unusable overload). Without the SFINAE guard, it's just say that there's no call operator to f. If you just used static_assert, you could get a very readable error (assertion failed: can_be_called_with_3), and then template spew. Since they both spew, and most people know to look for the first error out of habit, it seems like SFINAE with enable_if is overkill (unless the OP needs overloads). – Robert Mason Apr 12 '13 at 18:27
  • Actually I do want to have overloaded methods. Besides the `call(const char *, Args..., Functor)` method, I also have a `call(const char *, void **args, Functor)`. – Kurt Pattyn Apr 13 '13 at 07:28
  • @kornp That should not need SFINAE however. Preference of non variardic should be enough. – Yakk - Adam Nevraumont Apr 13 '13 at 14:16
0

if you want to vhave a pack of arguments as template you cannot write this in a way you already did:

template <typename...Args, typename Func>
void call(const char *name, Args...args, Func f)
{
        f(3);
}

But you can pack them in a std::tuple:

template <typename...Args, typename Func>
void call(const char *name, std::tuple<Args...> args, Func f)
{
        f(3);
}

call("test", std::forward_as_tuple(1, 2, 3), [=](int i) { std::cout<< i; });
4pie0
  • 29,204
  • 9
  • 82
  • 118