6

Given a non-variadic function template:

template<class T>
void f(void(t)(T));

And some plain functions:

void f1(int);
void f2(char);

This works:

f(f1);

The type of t becomes void (*)(int).

However, the variadic counterpart:

template<class... T>
void f(void(...t)(T));

// call
f(f1, f2);

does not work. The compilers (gcc & clang) complain about mismatched types void(T) and void (*)(int). See DEMO.

Note that if * is added explicitly, it works as it should:

template<class... T>
void f(void(*...t)(T));

So, why the non-variadic one can decay the function type while the variadic one cannot?

Jamboree
  • 5,139
  • 2
  • 16
  • 36
  • Incomplete description of the problem. Calling code for `template void f(void(...t)(T));` which produces the error? Compiler version? Error message? – Adrian Colomitchi Aug 31 '16 at 02:27

3 Answers3

3

AFAICS, the code is fine (also supported by the fact that both VC++ and ICC compile it). After all, template argument deduction seems to work with function types just as it does with function pointer or reference types; [temp.deduct.type]/18:

A template-argument can be deduced from a function […] type.

[temp.deduct.call]/1:

For a function parameter pack that occurs at the end of the parameter-declaration-list, deduction is performed for each remaining argument of the call, taking the type P of the declarator-id of the function parameter pack as the corresponding function template parameter type. Each deduction deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack.

In particular the latter paragraph confirms that there is some inconcistency, since the (unsuccessful) deduction of the pack T in the second case reduces to the (successful) deduction in case 1.

My guess is that Clang and GCC decay the parameter types for function templates right at declaration time, but refuse to do so when the parameter is a pack expansion (and then fail to deduce). Clang's error message when we alter the sample call to f(f1) is

note: candidate template ignored: could not match 'void (T)' against 'void (*)(int)'

So the argument is in fact decayed before deduction.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • I just examined the Clang [AST](http://coliru.stacked-crooked.com/a/ffbfa55ed0179d32) and it seems your guess is right, but is such behavior specified by the standard or is it an oversight? – Jamboree Sep 01 '16 at 01:33
  • @Jamboree Well I thought my quotings would suffice to convince you ;) – Columbo Sep 01 '16 at 11:06
  • I don't see anywhere you quoted specifies that the compiler should decay the parameter types except for pack expansion at declaration time? – Jamboree Sep 01 '16 at 13:34
  • @Jamboree No, that's why it's a bug. – Columbo Sep 01 '16 at 13:37
0

Because your syntax is not quite right.

template<class... T>
void f(void(t)(T...));

void f1(int);

void foo()
{
    f(f1);
}

Tested with gcc 6.1.1. Compiles without errors.

In the non-variadic version, you are using the template parameter as the parameter to the arguments to the passed function parameter.

Therefore, for the variadic version, that's where the parameter pack gets expanded.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
0

Does this explains?

template<class... T>
void f(void(*...t)(T)) {
  std::cout << "Ptr to functions" << std::endl;
}

template<class... T>
void g(void(&...t)(T)) {
  std::cout << "Ref to functions" << std::endl;
}

template <class... T>
void h(void(...t)(T)) {
  std::cout << "Mmmm... potential of confusion" << std::endl;
}

void s1(int) {

}

void s2(long) {

}


#include <type_traits>
int main() {
  // this compiles Ok. The compiler knows
  //    pointer to functions will be passed
  //    and can infer their correspondent T
  f(s1,s2);

  // this compiles Ok. The compiler knows that
  //   reference to functions will be passed
  //   and can infer their correspondent T
  g(s1,s2);

     h(s1,s2);
  // ^^^^^^^^^ this will cause a compilation error telling:
  //   template argument deduction/substitution failed:
  //   mismatched types ‘void(T)’ and ‘void (*)(int)’
  // The compiler can't decide how the function-type parameters are passed
  //  by ptr, by ref, one by ref other by ptr? As such
  //  cannot deduce their correspondent T in the argpack 


  // however, this will compile OK!!!
  // The s1/s2 correspondent T-es in argpack are clearly specified, 
  // not a problem.
  h<int,long>(s1,s2);
  return 0;
}
Adrian Colomitchi
  • 3,974
  • 1
  • 14
  • 23
  • 3
    This just repeats the problem I shown. Note that the non-variadic one does work w/o explicit `*` or `&`, the question is why this is not the case for variadic one. – Jamboree Aug 31 '16 at 03:47
  • @Jamboree - "This just repeats the problem I shown." no, it doesnt. I'll update the answer to make it obvious. – Adrian Colomitchi Aug 31 '16 at 04:11
  • @Jamboree Actually, what is the problem? "Why variadic templates cannot deduce the args without help" or "I understand why variadics cannot deduce, I wonder how come the single param template can"? – Adrian Colomitchi Aug 31 '16 at 04:17
  • This is intended to be a "language-lawyer" kind question. I want to know if the standard mandates the inconsistency (why and where), or is it a standard defect. – Jamboree Aug 31 '16 at 05:51
  • @Jamboree while I could infer the answer to the first, I don't know the answer for the second ("How come the non-variadic can"). And certainly IANA**LL** . Perhaps if you adjust your question to specifically mention your need for an LL, then the chances to find someone to answer it increase (you made me curious too). – Adrian Colomitchi Aug 31 '16 at 06:17