Home > Back-end >  Strange compilation errors when instantiating a variadic function template
Strange compilation errors when instantiating a variadic function template

Time:06-14

Let's first introduce a helper type that represents a parameter pack:

template<typename... T> struct Pack { };

Now, here's the function with the weird behaviour:

template<typename... TT, typename T>
void f(Pack<TT...>, Pack<T>, std::type_identity_t<TT>..., std::type_identity_t<T>);

std::type_identity_t is used here to disable deduction from the last two parameters in case that would introduce any ambiguity.

First, I tried to call it like so:

f(Pack<int>{}, Pack<int>{}, 5, 5);

GCC raises an error and gives the following explanation:

<source>:12:6: note:   candidate expects 3 arguments, 4 provided
   12 |     f(Pack<int>{}, Pack<int>{}, 5, 5);
      |     ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

That's not the kind of error I expected, I assumed that deduction would result in T = int and TT... = int. But okay, let's do what the note says and provide one less argument:

f(Pack<int>{}, Pack<int>{}, 5);

It still gives an error, but this time it's:

<source>:12:6: error: too few arguments to function 'void f(Pack<TT ...>, Pack<T>, std::type_identity_t<TT>..., std::type_identity_t<T>) [with TT = {int}; T = int; std::type_identity_t<T> = int]'
   10 |     f(Pack<int>{}, Pack<int>{}, 5);
      |     ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now it's too few of them. Also notice that this error message confirms my assumption about the deduced T and TT....

At this point I switched to Clang to see whether it'd compile this code, but both of the above calls to f (with 3 and 4 arguments) result in the same error, with the note:

<source>:8:6: note: candidate template ignored: deduced packs of different lengths for parameter 'TT' (<int> vs. <>)
void f(Pack<TT...>, Pack<T>, std::type_identity_t<TT>..., std::type_identity_t<T>);
     ^

I also tried compiling the code with MSVC, and it did compile without errors.

What is going on? Is there something that makes this code invalid? Is it a compiler bug, due to the strange error messages?

CodePudding user response:

std::type_identity<TT>... is mentioned in the comments as an way to remove the third function parameter (which is a pack) from argument deduction, but this has no effect as a function parameter pack that does not occur at the end of a parameter list is in a non-deduced context anyway; as per [temp.deduct.type]/5.7:

/5 The non-deduced contexts are:

  • [...]
  • /5.7 A function parameter pack that does not occur at the end of the parameter-declaration-list.

Then why can't the parameter pack be unambiguously deduced from the first function parameter (Pack<TT...>)? This would arguably make sense for C developers, however it may end up as a quality of implementation issue for implementors, as it would start mixing template argument deduction with overload resolution. It would require two-pass template argument deduction where first a parameter pack is unambigously deduced whereafter from say one function parameter, after which the result is used to modify the same function's parameter list (to expand an otherwise non-deducible parameter pack) and thereafter return to template argument deduction for the modified function template. This is not how template argument deduction works anywhere else, possibly solely due to quality of implementation.

[temp.deduct.call]/1 does mention that such a pack is never deduced (emphasis mine):

[...] When a function parameter pack appears in a non-deduced context ([temp.deduct.type]), the type of that pack is never deduced.

It could be argued whether or not this intentionally rejects packs that could be deduced from anywhere else. If so, the OP's program is indeed ill-formed, however the error messages of the compilers is not very helpful in diagnosing this as the root cause (if it it). It seems as if compilers may rely on [temp.deduct.call]/1 to actually deduce non-deducible template parameter packs to empty packs.


We may finally note that [temp.deduct.call]/1 supports the use of explicit template arguments, as this is not deduction:

#include <type_traits>

template<typename... T> struct Pack { };

// Note the swap of template argument positions
// to allow explicitly providing template arguments.
template<typename T, typename... TT>
void f(Pack<TT...>, Pack<T>, std::type_identity_t<TT>..., std::type_identity_t<T>) {}

int main() {
    f<int, int, int>(Pack<int, int>{}, Pack<int>{}, 1, 2, 3);
}
  • Related