M. Yuan
M. Yuan

Reputation: 43

Variadic template, no matching function for call

I'm trying to use variadic template to refactor some of my code, but the compiler has "no matching function for call" error. Below is a simplified version (it may not make sense for functionality, but an example to reproduce the error):

// base case
void testFunc(int i) { std::cout << i << std::endl; }

template <class T, class... Args> void testFunc(int i) {
  T t = 0;
  std::cout << t << std::endl;
  testFunc<Args...>(i);
}

int main() {
  testFunc<int, long, float>(1);
  return 0;
}

The error messages:

main.cpp:9:3: error: no matching function for call to 'testFunc'
  testFunc<Args...>(i);
  ^~~~~~~~~~~~~~~~~
main.cpp:9:3: note: in instantiation of function template specialization 'testFunc<float>' requested here
main.cpp:9:3: note: in instantiation of function template specialization 'testFunc<long, float>' requested here
main.cpp:13:3: note: in instantiation of function template specialization 'testFunc<int, long, float>' requested here
  testFunc<int, long, float>(1);
  ^
main.cpp:6:40: note: candidate template ignored: couldn't infer template argument 'T'
template <class T, class... Args> void testFunc(int i) {
                                       ^
1 error generated.

It looks like that the unwrapping of template parameters is working, and stops at the base case. But I have defined the base case. Why there is no matching function?

Upvotes: 4

Views: 1961

Answers (3)

Picaud Vincent
Picaud Vincent

Reputation: 10982

As explained before the problem comes from the fact that testFunc<int>(int i) tries to call testFunct<>(int t)

However in C++ testFunct<>(int t) is different from testFunct(int t)

Also note that in C++ you can not partially specialize functions, as explained here for instance.

One solution that is close to your approach is to define

// Stop recursion
template <class T>
void testFunc(int i)
{
  T t = 0;
  std::cout << t << " " << typeid(T).name() << std::endl;
}

and to avoid ambiguous definition thanks to SFINAE

// instantiated only when Args... is not "empty"
template <class T, class... Args>
typename std::enable_if<sizeof...(Args)>::type testFunc(int i)
{
  T t = 0;
  std::cout << t << " " << typeid(T).name() << std::endl;
  testFunc<Args...>(i);
}

This is perfectly legal C++11 and it follows closely your initial guess


Complete running code: to compile with g++ -std=c++11 testVariadic.cpp; ./a.out

#include <iostream>
#include <type_traits>

template <class T>
void testFunc(int i)
{
  T t = 0;
  std::cout << t << " " << typeid(T).name() << std::endl;
}

template <class T, class... Args>
typename std::enable_if<sizeof...(Args)>::type testFunc(int i)
{
  T t = 0;
  std::cout << t << " " << typeid(T).name() << std::endl;
  testFunc<Args...>(i);
}

int main()
{
  testFunc<int, long, float>(1);
  return 0;
}

Output:

0 i
0 l
0 f

Upvotes: 1

YSC
YSC

Reputation: 40160

For a single type, your function is not well-defined since it tries to call void testFunct<>(int) which differ from void testFunc(int i).

You could test the size of your parameter pack before recursing using C++17's constexp if:

template <class T, class... Args> void testFunc(int i) {
  T t = 0;
  std::cout << t << std::endl;
  if constexpr (sizeof...(Args) > 0) {
    testFunc<Args...>(i);
  } else {
    testFunc(i);
  }
}

Upvotes: 3

max66
max66

Reputation: 66230

The problem is that calling

testFunc<Args...>(i);

you call the template version of testFunc(), not the base case version.

And when Args... is empty, there isn't a template version available.

To solve this problem... if you can use C++17, you can use if constexpr, as suggested by YSC.

For C++11 and C++14, I propose to use the template partial specialization of a struct.

The following is a full working example

#include <iostream>

// base case  
template <typename...>
struct foo
 { static void bar (int i) { std::cout << i << std::endl; } };

// recursive case
template <typename T, typename ... Args>
struct foo<T, Args...>
 {
   static void bar (int i)
    {
      std::cout << T{} << std::endl;

      foo<Args...>::bar(i);
    }
 };

int main()
 {
   foo<int, long, float>::bar(1);
 }

Upvotes: 3

Related Questions