c++templatesparameter-pack

definition dependent template deduction failure


Within a larger project I got issues when trying to use templates to define multiple classes. After some tinkering I found out, that my code was working when I changed the order of the template definition. Since I'm rather trying to find the issue in my implementation than blaming the compiler for doing something wrong, I would really appreciate if someone could give me the reason for the following behavior.

I'm using ggc on ubuntu gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 and build the example with the compile feature cxx_std_20.

UPDATE: Thanks for all the comments on my first post, given the tips on how to improve the way I was asking my question I try to simplify the question with this update.

Given the small example:

#include <cstddef>

template <std::size_t... NUMBERS>
struct NumberList
{};
template <typename... TYPES>
struct TypeList
{};

template <typename ...TYPES, template <typename ...> typename List, std::size_t number, std::size_t... numbers>
//template <std::size_t number, std::size_t... numbers, typename... TYPES, template <typename...> typename List>
constexpr void loadNumbers(const List<TYPES...>& types)
{
    // do something 
}

template <std::size_t... numbers, typename... TYPES>
constexpr void loadNumbersAndTypes(const NumberList<numbers...>& number, const TypeList<TYPES...>& types)
{
  loadNumbers<TYPES..., TypeList<TYPES...>, numbers...>(types);
  //loadNumbers<numbers...>(types);
}

int main()
{
  NumberList<2, 3, 4, 5, 6, 7, 8, 9, 10> numbers;
  TypeList<float, double> types;
  loadNumbersAndTypes(numbers, types);

  return 0;
}

(tested also in godbolt) the compiler is not able to infer the template parameter when calling loadNumbers. The error tells that there is a type/value mismatch, i.e. the first argument expects a type but got '2'. However, the first template arguments explicitly given when calling the function are clearly types and not the std::size_ts as complained by the compiler.

The exact compiler output states:

<source>: In instantiation of 'constexpr void loadNumbersAndTypes(const NumberList<numbers ...>&, const TypeList<TYPES ...>&) [with long unsigned int ...numbers = {2, 3, 4, 5, 6, 7, 8, 9, 10}; TYPES = {float, double}]':
<source>:28:22:   required from here
<source>:20:56: error: no matching function for call to 'loadNumbers<float, double, TypeList<float, double>, 2, 3, 4, 5, 6, 7, 8, 9, 10>(const TypeList<float, double>&)'
   20 |   loadNumbers<TYPES..., TypeList<TYPES...>, numbers...>(types);
      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
<source>:12:16: note: candidate: 'template<class ... TYPES, template<class ...> class List, long unsigned int number, long unsigned int ...numbers> constexpr void loadNumbers(const List<TYPES ...>&)'
   12 | constexpr void loadNumbers(const List<TYPES...>& types)
      |                ^~~~~~~~~~~
<source>:12:16: note:   template argument deduction/substitution failed:
<source>:20:56: error: type/value mismatch at argument 1 in template parameter list for 'template<class ... TYPES, template<class ...> class List, long unsigned int number, long unsigned int ...numbers> constexpr void loadNumbers(const List<TYPES ...>&)'
   20 |   loadNumbers<TYPES..., TypeList<TYPES...>, numbers...>(types);
      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
<source>:20:56: note:   expected a type, got '2'
<source>:20:56: error: type/value mismatch at argument 1 in template parameter list for 'template<class ... TYPES, template<class ...> class List, long unsigned int number, long unsigned int ...numbers> constexpr void loadNumbers(const List<TYPES ...>&)'
<source>:20:56: note:   expected a type, got '3'
[...]

When using the commented lines instead (switching the order in which the template arguments are defined), everything works like a charm. I'm not understanding, why this order is of such important here.


Solution

  • However, the first template arguments explicitly given when calling the function are clearly types and not the std::size_ts as complained by the compiler.

    No, you are looking at the code like a human instead of like a compiler. The compiler takes your tidy <TYPES..., TypeList<TYPES...>, numbers...> and substitutes in TYPES and numbers, resulting in a list of types followed by a list of numbers (TypeList<TYPES...> is a type, not a template). The compiler then ignores what you wrote and instead uses this list of types and numbers as the template arguments. The same result could be obtained by writing out each template argument instead of using pack expansion. That is, "explicitly given" is not relevant for determining which arguments are "the first template arguments".

    It does not help that you specified a type, TypeList<TYPES...>, for your template template parameter instead of a template, TypeList. However, that is not the main problem here.


    The main problem is a requirement I'll quote from cppreference.com. It basically says that you can have non-pack template parameters, followed by a single paramater pack, followed by parameters (possibly including packs) deduced from function arguments, followed by parameters with defaults.

    In a function template, the template parameter pack may appear earlier [than the final parameter] in the list provided that all following parameters can be deduced from the function arguments, or have default arguments:

    In loadNumbers, the parameter pack TYPES is not the final parameter. Among the parameters that come after it are number and numbers, neither of which influence the function arguments, and neither of which have default arguments. Therefore your function template definition is ill-formed.

    In order to make your definition work, you need the deduced parameter pack to come after the explicitly-provided pack, as in the commented-out version. The parameter pack TYPES (which can be deduced from the function arguments) must appear after the parameter pack numbers (which cannot be deduced). The List parameter can also be deduced from the function arguments, but it is not a parameter pack, so it may appear either before or after numbers.

    In a comment, you mentioned loadNumbersAndTypes() having two parameter packs. In this case, all template parameters are deduced from the function arguments, so multiple packs are allowed, and the packs can occur in any order.


    It is unfortunate that the error message did not call out the main problem. However, it is common for ill-formed templates to produce unclear error messages.