variadic template function declaration

Hi, guys. I just read that when we define a recursive variadic template function, the nonvariadic version of the template function should always be declared before the definition of the variadic verison. I tested it with the code shown below, and it is true. If you reversed the order of the definition of the the variadic version and the nonvariadic version here, it would generate an error.
1
2
3
4
5
6
7
8
9
10
11
template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t;
}
template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);
}

But I cannot make sense of this. I mean even if the nonvariadic version it not declared yet when the variadic version is defined, when we use this overload function later on, as long as the case comes down to a function call with two arguments, say "cout" and "arg1", both variadic version and nonvariadic version would be instantiated, and the nonvariadic version would always be a better call. there doesn't seem to be any differences with the nonvaridic version being declared before or after.
Can someone help me out? Thanks!
Last edited on
The compiler takes the first function that suits the signature of the called function. If the compiler finds a function with the matching signature with an error it will state the error. It doesn't make sense to search for the same function just without error.

For templates it means you need to provide the function without error first (and hope that the compiler uses it first). This is just how the compiler works.
Let me rephrase what you said and see if I get it right. So you were saying that in this case if we defined the variadic version first, then when I run this program with several arguements (at least 2 here including an ostream& type), the nonvariadic version would not even be instantiated?
the nonvariadic version would not even be instantiated?
Yes, the reason is that print() is resolved recursively. While being stuck in the recursion the compiler cannot look elsewhere in the project. The function that provides the way out must be known beforehand.

Honestly I'm surprised that this works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

using namespace std;

template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);

}
template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t;
}

int main ()
{
    print(cout, 1);

  return 0;
}
1
I'd guess that the compiler knows both versions and prefers the non variadic

[EDIT]
One more thing: An error within a template is not always an error:

http://en.cppreference.com/w/cpp/language/sfinae
Last edited on
Thanks for the clarification, but I still have some doubts. I'm currently reading this book "C++ Primer" by Lippman, it mentions about a set of rules for function matching and one is that if there are a few candidate template functions which are equally good in a function call, then the most specialized one would be selected. If I'm not mistaken here, following what you said, there won't even be a case where there are several equally good candidate functions, because it would always take the first viable function.
Moreover, how could you explain the fact that this code works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

using namespace std;

template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);

}
template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t;
}

int main ()
{
    print(cout, 1);

  return 0;
}

The function call in the main function should only instantiate the variadic version and that would be a error. More importantly, it should never bother instantiating the nonvariadic version, is it?
Well, obviously the non variadic version is considered more specialized than the variadic. Remember it's a difference whether the compiler knows the functions or not.

In the main function both print() functions are known. Due to the specialization criterion the compiler is actually able to differentiate (if not it would throw an error)
Sorry, I still do not quite get the idea. As I interpret what you said, if there were several candidate template functions when a function call was made, the compiler would "know" that there are this many of candidate functions, and it would only instantiate the most suitable one (i.e. the most specialized one) and leave the rest uninstantiated? And following this logic, if the code was written as:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

using namespace std;

template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t;
}

template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);

}

int main ()
{
    print(cout, 1);

  return 0;
}

only the non-variadic version would be instantiated.
And if this is indeed what you meant, then is it contradicting with what you said ealier that
The compiler takes the first function that suits the signature of the called function.
, which was basically saying that even if there are a few candidate template functions, and the compiler "knows" them all, it will not check which one is the most specialized, but just simply
the first function that suits the signature of the called function
.
As I interpret what you said, if there were several candidate template functions when a function call was made, the compiler would "know" that there are this many of candidate functions, and it would only instantiate the most suitable one (i.e. the most specialized one) and leave the rest uninstantiated?
Yes

And if this is indeed what you meant, then is it contradicting with what you said ealier that
No. Take a closer look at the variadic function. It does not match the signatur (due to const Args&... rest). If there are no matching functions the compiler will see if there are other options that can be used:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>

using namespace std;

template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << "p " <<  t;
}

template<typename T, typename T2>
ostream &print(ostream &os, const T &t, const T2 &t2)
{
    return os << "p2 " << t << t2;
}

template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);

}

int main ()
{
    print(cout, 1);
    cout << endl;
    print(cout, 1, 2);
    cout << endl;
    print(cout, 1, 2, 3);

  return 0;
}
p 1
p2 12
1, p2 23
Does that mean that variadic functions do not match any kind of signature? Since variadic functions will always have a parameter as const Args&... rest.

And what exactly do you mean by match the signature? Is it referring to exact match without any sorts of conversion? For example, take a look at this code
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

using namespace std;

template<typename T> void foo(const T &t) { }

int main()
{
    int a = 1;
    foo(a);
}

Does foo() here match the signature of foo(a)?
If a function template is overloaded, the use of a function template specialization might be ambiguous because template argument deduction may associate the function template specialization with more than one function template declaration. Partial ordering of overloaded function template declarations is used in the following contexts to select the function template to which a function template specialization refers
— during overload resolution for a call to a function template specialization
...


Lots of arcane standardese here (search for "partial ordering"): http://en.cppreference.com/w/cpp/language/function_template#Function_template_overloading

This is what I think:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>

template < typename T > void foo( const T& ) { std::cout << "foo - not variadic, arity one\n" ; }
template < typename T > void foo( const T&, const T& ) { std::cout << "foo - non-variadic, arity two\n" ; }

template < typename... T > void foo( const T&... ) { std::cout << "foo - variadic, arity zero or more\n" ; }
template < typename A, typename... B > void foo ( const A&, const B&... ) { std::cout << "bar - variadic, arity one or more\n" ; }

int main()
{
    foo() ; // arity zero => "foo - variadic, arity zero or more\n"
    // the only viable function is: template < typename... T > void foo( const T&... )

    foo(1) ; // arity one => foo - not variadic, arity one
    // viable functions are:
    // template < typename T > void foo( const T& )
    // template < typename... T > void foo( const T&... )
    // use partial ordering: non-variadic template is selected over the variadic template  
    // the function template specialization refers to the non-variadic template

    foo( 1, 2 ) ; // arity two => foo - not variadic, arity two
    // viable functions are:
    // template < typename T > void foo( const T&, const T& )
    // template < typename... T > void foo( const T&... )
    // template < typename A, typename... B > void foo ( const A&, const B&... )
    // use partial ordering: non-variadic template is selected over the variadic template  
    // the function template specialization refers to the non-variadic template

    foo( 1, 2, 3 ) ; // arity three => foo - not variadic, arity one or more
    // viable functions are:
    // template < typename... T > void foo( const T&... )
    // template < typename A, typename... B > void foo ( const A&, const B&... )
    // both are variadic templates
    // use partial ordering: the template with more non-variadic template parameters is selected 
    // the function template specialization refers to  template < typename A, typename... B > void foo ( const A&, const B&... )
    // *** caveat ***: AFAIK
}

The LLVM and GNU compilers agree with this.
clang++ -std=c++14 -stdlib=libc++ -O3 -Wall -Wextra -pedantic-errors main.cpp -lsupc++ && ./a.out
echo --------- && g++ -std=c++14 -O3 -Wall -Wextra -pedantic-errors main.cpp && ./a.out
foo - variadic, arity zero or more
foo - not variadic, arity one
foo - non-variadic, arity two
bar - variadic, arity one or more
---------
foo - variadic, arity zero or more
foo - not variadic, arity one
foo - non-variadic, arity two
bar - variadic, arity one or more


Microsoft does not; I'm quite certain that their compiler is just plain wrong here:
http://rextester.com/LFUD4440
I agree with what you said about function deduction about the four overload foo().
But in this case,
1
2
3
4
5
6
7
8
9
10
11
template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);
}
template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t;
}

Is the instantiantion of the the variadic function with 2 arguments(one ostream& type, one some other type, say A) considered as being equivalent to the instantiation of the nonvariadic function that's instantiated with the same set of arguments ( one ostream& type and one A type), and therefore, the nonvariadic type would never be instantiated since it says here http://en.cppreference.com/w/cpp/language/function_template#Function_template_overloading that
If multiple declarations of the same template differ in the result of name lookup, the first such declaration is used:
Last edited on
I guess I got it wrong in the previous reply.
If multiple declarations of the same template differ in the result of name lookup, the first such declaration is used
is talking about redeclaration of functions, and it should have nothing to do with overloading.
But why exactly in this code
1
2
3
4
5
6
7
8
9
10
11
template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", ";
    return print(os, rest...);
}
template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t;
}

the non-variadic function would not be instantiated? For instance, if I initially supplied three arguments, i.e. print(cout, 1, 2), it would instantiate the variadic version
1
2
 template<int, int>
ostream &print(ostream &, const int &, const int&)
, output1 and then call print(cout,2).
When print(cout,2) is called, there should be 2 viable functions, namely the variadic function with 2 parameters (ostream&,const int&) and the non-variadic function, and clearly the non-variadic one should be the better match in the case.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <sstream>

static int n = 0 ;

template < typename T >
std::ostream& print( std::ostream& stm, const T& t ) ; // **** declare #2

template < typename T, typename... ARGS >
std::ostream& print( std::ostream& stm, const T& t, const ARGS&... args ) // #1
{ 
    std::clog << "  " << ++n << ". variadic: std::ostream& print( std::ostream& stm, const T& t, const ARGS&... args ) // #1\n" ; 
    return print( stm << t, args... ) ; 
}

template < typename T >
std::ostream& print( std::ostream& stm, const T& t ) // #2
{ 
    std::clog << "  " << ++n << ". non-variadic: std::ostream& print( std::ostream& stm, const T& t ) // #2\n" ; 
    return stm << t  ; 
}

int main()
{
    std::stringbuf strbuf ;
    const auto oldbuf = std::clog.rdbuf( std::addressof(strbuf) ) ;
    print( std::cout, 1, 2 ) << '\n' << std::flush ;
    std::cout << "sequence of calls were:\n" << strbuf.str() ;
    std::clog.rdbuf(oldbuf) ;
}

clang++ -std=c++14 -stdlib=libc++ -O3 -Wall -Wextra -pedantic-errors main.cpp -lsupc++ && ./a.out
echo --------- && g++ -std=c++14  -O3 -Wall -Wextra -pedantic-errors main.cpp && ./a.out
12
sequence of calls were:
  1. variadic: std::ostream& print( std::ostream& stm, const T& t, const ARGS&... args ) // #1
  2. non-variadic: std::ostream& print( std::ostream& stm, const T& t ) // #2
---------
12
sequence of calls were:
  1. variadic: std::ostream& print( std::ostream& stm, const T& t, const ARGS&... args ) // #1
  2. non-variadic: std::ostream& print( std::ostream& stm, const T& t ) // #2

http://coliru.stacked-crooked.com/a/f9c60a82675d2f33
http://rextester.com/FCCXK65881
Topic archived. No new replies allowed.