Since clang++ shows a different (and strange) behaviour in this case, I'm not entirely sure of this answer. However, I think I can explain what's going on when using g++.
Let's take a look at the following example:
#include <iostream>
template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...))
{
std::cout << __PRETTY_FUNCTION__ << "\n";
}
void bar(int, bool, double, void*, float) {}
int main() {
foo(&bar);
}
__PRETTY_FUNCTION__
is a non-standard macro that I often use as a hack to allow printing the types a compiler has deduced. In this case, when compiled with g++, the program prints:
void foo(void (*)(ARGS1 ..., ARGS2 ...))
[with ARGS1 = {}; ARGS2 = {int, bool, double, void*, float}]
As we can see, g++ has not deduced anything for ARGS1
, but deduced all the types of the function parameters of bar
for ARGS2
.
Template argument deduction works for each function parameter separately. If a template argument T
is deduced from two function arguments, the deduced types must be identical (with few exceptions). Similarly, for parameter packs. Consider:
template<typename T>
void foo(T, T) {}
foo(1, 2.3)
This won't compile, since for the first function parameter, T
is deduced to be int
, but for the second function parameter, T
is deduced to be double
.
Similarly,
template<typename... T>
void foo(tuple<T...>, tuple<T...>) {}
foo(tuple<int, double>{}, tuple<bool, char>{})
There are inconsistent result for the deduction of T...
for the first and second function parameter. This inconsistency results in a compilation error.
As we have seen in the first part of this answer, for a parameter like:
template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...))
ARGS1
won't be deduced at all, and ARGS2
will "eat" all the function parameter types of the argument you pass in. If we now deduce ARGS2
also from another function argument:
template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...), tuple<ARGS2...>)
Then, the deduction results for ARGS2
have to be consistent:
void bar(int, double);
tuple<int, double> t;
foo(&bar, t); // works
tuple<double> u;
foo(&bar, u) // fails: inconsistent deduction results.
A very crude way to make the code in the OP compile is to tell the compiler not to deduce ARGS2
from the third argument. This can be achieved by putting the type of the third parameter in a non-deduced context (a context where deduction does not take place):
template<typename T>
struct identity_t
{ using type = T; };
template<typename T>
using non_deduced_context = typename identity_t<T>::type;
template<typename ...ARGS1, typename ...ARGS2>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &,
non_deduced_context<void (*)(ARGS1..., ARGS2...)>)
{
//call function
}
For a parameter like some_class_template<T>::nested_type
, T
is in a non-deduced context. This is used in the above example, though with some syntactic sugar.
The usual way of passing a function to another function in C++ is different, though:
template<typename F>
void foo(F f)
{
//call function, e.g.
f(42);
}
This allows passing arbitrary function objects, including pointers to functions.