4

I am currently studying variadic templates, and as a small exercise to digest some of the stuff I had been reading about, I wrote a small function to output the names of all of its argument types:

#include "stdafx.h"
#include <iostream>
#include <string>

template<typename Next, typename... Rest>
std::string get_arg_types(Next next, Rest... rest)
{
    return std::string(typeid(Next).name()) + "\n" + get_arg_types(rest...);
}

template<typename Last>
std::string get_arg_types(Last last)
{
    return std::string(typeid(Last).name());
}

int main()
{
    float f = 0;
    double d = 0;
    int i = 0;

    std::cout << get_arg_types(f, d, i);

    return 0;
}

To my surprise, this compiled using VC++ 12.0 and (seems to) work just fine.

I expected an error due to ambiguity between the overload when there is only one argument remaining, because I read that a template parameter pack can be empty/contain 0 arguments.

So my question is why does this work? How is the "potential ambiguity" resolved? Are the signature for both functions not identical with only 1 arg? I feel like I may have missed some important concept somewhere, because in my head the above example shouldn't compile, but obviously I am wrong.

Kind regards :)

not an alien
  • 651
  • 4
  • 13
  • 2
    Possible duplicate of [Ambiguous call when recursively calling variadic template function overload](https://stackoverflow.com/questions/42063084/ambiguous-call-when-recursively-calling-variadic-template-function-overload) (Edit: the duplicate's question is slightly different but the answer does kinda cater to this...) – TrebledJ Nov 23 '18 at 11:29
  • @TrebuchetMS Thank you for the link, but I feel the answers for that question don't really tell me anything more than "in my example, there will be no ambiguity". I already know that. What I want to know is why (i.e. how is the potential ambiguity resolved) – not an alien Nov 23 '18 at 11:52
  • 2
    Concerning the possible duplicate link of TrebuchetMS: This is what [Jarods answer](https://stackoverflow.com/a/42234945/7478597) explains (in the yellow box): _if one function template has a trailing parameter pack and the other does not, the one with the omitted parameter is considered to be more specialized than the one with the empty parameter pack._ Doesn't this answer why it works for the last? – Scheff's Cat Nov 23 '18 at 11:59

1 Answers1

1

Your code is problematic, but for a different reason. As noted in a answer to the related question Ambiguous call when recursively calling variadic template function overload, the second overload in your code is considered more specialized. However, it should appear before the routine with the parameter pack. Compiling your code with gcc 8.2.1 or clang 6.0.1 gives an error like

variadic.cpp: In instantiation of ‘std::__cxx11::string get_arg_types(Next, Rest ...) [with Next = int; Rest = {}; std::__cxx11::string = std::__cxx11::basic_string<char>]’:
variadic.cpp:7:67:   recursively required from ‘std::__cxx11::string get_arg_types(Next, Rest ...) [with Next = double; Rest = {int}; std::__cxx11::string = std::__cxx11::basic_string<char>]’
variadic.cpp:7:67:   required from ‘std::__cxx11::string get_arg_types(Next, Rest ...) [with Next = float; Rest = {double, int}; std::__cxx11::string = std::__cxx11::basic_string<char>]’
variadic.cpp:23:39:   required from here
variadic.cpp:7:67: error: no matching function for call to ‘get_arg_types()’
     return std::string(typeid(Next).name()) + "\n" + get_arg_types(rest...);
error: no matching function for call to ‘get_arg_types()

As you can see from the error, even when get_arg_types has a single parameter, the compiler picks up the first overload, which in turns call get_arg_types without arguments. A simple solution is to move the overload of get_arg_types with a single parameter before the routine with the template parameter pack, or add a declaration of the single-parameter get_arg_types before the general routine.

Alternatively, you could eliminate your specialization single-parameter of get_arg_types and add a specialization with 0 parameters:

std::string get_arg_types()
{
  return std::string();
}

Again, such a specialization should be put (or at least declared) before the template routine. Notice that with this second solution the output is slightly changed.

francesco
  • 7,189
  • 7
  • 22
  • 49