4

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.

Tomeamis
  • 477
  • 6
  • 14
  • I'm not terribly familiar with vardic templates, but `typename ...Params, typename ...Args,` seems off. How do you expect the compiler to differentiate between the two? But again, not too familiar with vardic templates, so maybe I'm just missing something. –  Jun 06 '19 at 19:05
  • 1
    Two parameter packs can be used if they are in deduced contexts. See for example https://stackoverflow.com/questions/9831501/how-can-i-have-multiple-parameter-packs-in-a-variadic-template – Tomeamis Jun 06 '19 at 19:13
  • Why not just define `template struct And :std::bool_constant<(Args && ...)> {};`? – L. F. Jun 08 '19 at 01:52
  • @L.F. Because I don't have C++17. Also, if I did, I would probably just not define `struct And` at all, and just use the fold expression directly – Tomeamis Jun 09 '19 at 09:54

2 Answers2

5

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

erenon
  • 18,838
  • 2
  • 61
  • 93
  • That's why I put the standard quotation about overload sets in the question. Could you elaborate why it doesn't apply here? Thanks for is_invocable, though, I didn't know about that. – Tomeamis Jun 06 '19 at 19:17
5

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Oh, now that I look at it, SFINAE happens during overload resolution, which happens after template argument deduction. That's what I was looking for. Thanks – Tomeamis Jun 06 '19 at 19:23
  • 1
    @Tomeamis Not exactly. The compiler tries to deduce `Params...`, it can't, so it leave it as non deduced. Then it deduces `Args...`, gets that on moves on to the last parameter. Once it gets there you get a hard error because `Params...` wasn't deduced, and you need it to be to try and apply SFINAE. – NathanOliver Jun 06 '19 at 19:27