I have a template function that should take a function pointer and arguments, then invoke the function pointer with the given arguments (let's call it Invoke
). However, when I call the template function with an overloaded function as an argument, template deduction fails.
I have used enable_if so that only one overload would be valid, but this didn't help.
#include <string>
#include <type_traits>
void foo(int, int){}
void foo(std::string, std::string) {}
template <bool Val1, bool Val2, bool ...Rest>
struct And
{
enum {value = And<Val1 && Val2, Rest...>::value};
};
template <bool Val1, bool Val2>
struct And<Val1, Val2>
{
enum {value = Val1 && Val2};
};
template <typename ...Params, typename ...Args, typename = typename std::enable_if<
And<std::is_convertible<Args, Params>::value...>::value
>::type>
void Invoke(void (*fn)(Params...), Args ...args){}
int main() {
Invoke(&foo, "a", "b");
return 0;
}
Try on ideone.
The compiler complains that mismatched argument pack lengths while expanding ‘std::is_convertible<Args, Params>::value’
, when both overloads are present.
When I comment out the int
overload, the program compiles just fine, and when I comment out the std::string
overload, deduction fails as it should, since const char[]
is not implicitly convertible to int
.
The standard (section 17.8.2.1.6.2 of the C++17 standard) says the following:
If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a non-deduced context.
So I would expect that the compiler would try the int
overload, where deduction would fail. When it tries the std::string
overload, the deduction would succeed.
Since the deduction would succeed for only one of the overload set members, I would expect that it would then proceed as if the int
overload didn't exist, and the compilation would succeed like it does when the other overload is commented out, but it fails.
Where am I wrong?
References to standard will be appreciated.
The problem here is that there is more than one function that works. The deduction of Params...
is going to happen before you even get to the SFINAE part of the template. When it tries to deduce Params..
from void (*fn)(Params...)
it matches void foo(int, int)
and void foo(std::string, std::string)
. Since it finds multiple matches, 17.8.2.1.6.2 states that it is treated as a non deduced context.
Since it cant deduce a type, you get hard stop error. SFINAE only happens after the template parameter deduction step, which it can't get to in this case.
The &foo
is not a function pointer, but an overload set. You have to be explicit:
Invoke(static_cast<void(*)(std::string, std::string)>(&foo), "a", "b");
To simplify the failing enable_if
, you could take an unspecified function pointer type, with a variadic arugment pack, and check is_invocable
:
https://en.cppreference.com/w/cpp/types/is_invocable
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With