Kurt Pattyn
Kurt Pattyn

Reputation: 2798

How can a template argument after variardic arguments be inferred?

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.

Upvotes: 3

Views: 551

Answers (3)

ecatmur
ecatmur

Reputation: 157484

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)...));
}

Upvotes: 8

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275896

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.

Upvotes: 4

4pie0
4pie0

Reputation: 29754

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; });

Upvotes: 0

Related Questions