Reputation: 2798
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
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
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
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