2

I need your help to find out why does following code not compile.

#include <iostream>

template <int I>
void foo(){
  std::cout << I << std::endl;
  std::cout << "end of list" << std::endl;
}

template <int I, int ... Ints>
void foo(){
  std::cout << I << std::endl;
  foo<Ints...>();
}


int main(){
  foo<1, 2>();
  return 0;
}

I am getting this error.

function_parameter_pack.cpp: In instantiation of ‘void foo() [with int I = 1; int ...Ints = {2}]’:
function_parameter_pack.cpp:17:13:   required from here
function_parameter_pack.cpp:12:15: error: call of overloaded ‘foo<2>()’ is ambiguous
   foo<Ints...>();
   ~~~~~~~~~~~~^~
function_parameter_pack.cpp:4:6: note: candidate: void foo() [with int I = 2]
 void foo(){
      ^~~
function_parameter_pack.cpp:10:6: note: candidate: void foo() [with int I = 2; int ...Ints = {}]
 void foo(){

Isn't a more specialized function is supposed to be chosen? i.e one with only one template(the first one).

max66
  • 65,235
  • 10
  • 71
  • 111
atuly
  • 193
  • 1
  • 10
  • 2
    "More specialized" is done though parameters, not template parameter; as for empty pack tie-break. – Jarod42 Feb 16 '21 at 10:36
  • 1
    https://en.cppreference.com/w/cpp/language/overload_resolution#Best_viable_function and https://en.cppreference.com/w/cpp/language/function_template#Function_template_overloading – Jarod42 Feb 16 '21 at 10:42
  • For a C++17 solution, see https://stackoverflow.com/a/43070233 – Aykhan Hagverdili Feb 16 '21 at 11:08

3 Answers3

4

foo<2> matches both templates equally well (since int... matches zero ints too) which is why the compiler complains about ambiguity.

Isn't a more specialized function is supposed to be chosen?

Yes, but a these are equally special. Deducing which is the more specialized matching function is done by looking at the arguments to the function, not the template parameters.

One way to solve that is to make the second function template take 2 or more template parameters:

#include <iostream>

template<int I>
void foo() {
    std::cout << I << std::endl;
    std::cout << "end of list" << std::endl;
}

template<int I, int J, int... Ints>
void foo() {
    std::cout << I << std::endl;
    foo<J, Ints...>();
}

int main() {
    foo<1>();
    foo<1, 2>();
    foo<1, 2, 3>();
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 1
    Note that reason is (over-)simplified though, as `template void f(T); template void f(T, Ts...);` and `f(42);` would compile, even if `f` matches both template. – Jarod42 Feb 16 '21 at 10:56
  • @Jarod42 True. I know that part too bad to express why that is. Something about the non-type template parameters in the question I guess? I'll happily upvote if you put up an answer to explain it. :-) – Ted Lyngmo Feb 16 '21 at 11:03
  • Overload resolution has plenty of rules, and as my first comment, *""More specialized" is done though parameters, not template parameter"*. So, unrelated to type/non type template parameters. – Jarod42 Feb 16 '21 at 11:14
  • @Jarod42 Ah, I didn't notice that comment. Thanks. I put that in the answer and I tried using my own words. I hope I didn't mess it up. – Ted Lyngmo Feb 16 '21 at 11:30
  • @Jarod42 Could you please explain why would the code you wrote in comment compile but not the one asked in question. Isn't is the same? Am I missing something? – atuly Feb 16 '21 at 14:11
  • 1
    My functions use **parameters/arguments** (in addition to template parameters). – Jarod42 Feb 16 '21 at 14:17
3

Another way to solve the problem is using a do-nothing terminal recursion function accepting something different from a value (maybe a typename), but with a default,

template <typename = void>
void foo() {
  std::cout << "end of list" << std::endl;
}

that intercept a foo<Ints...>() call when Ints... is empty.

The recursive case, that intercept every template integer value, remain the same

template <int I, int ... Ints>
void foo(){
  std::cout << I << std::endl;
  foo<Ints...>();
}
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    Another way is to get rid of recursion: `template void foo() { for (int i : {Is...}) {std::cout << i << std::endl;} std::cout << "end of list\n";}` :-) (or C++17 fold expression `((std::cout << Is << std::endl), ...);` (or equivalent trick for C++11)). – Jarod42 Feb 16 '21 at 10:47
  • @Jarod42 - I know... and is better avoid recursion... but I interpret the question more about the recursion management than the execution of the body of the functions. – max66 Feb 16 '21 at 10:51
2

I prefer solution which uses dummy array, so it works similarly to fold expression.

template<int... Ints>
void foo() {
    char dummy[] {((std::cout << Ints << ','), '0')...};
    std::cout << '\n';
}

https://godbolt.org/z/1Y1noK

Marek R
  • 32,568
  • 6
  • 55
  • 140