递归可变参数模板函数 - 没有歧义?

Recursive Variadic Template Function - No ambiguity?

我目前正在研究可变模板,作为一个小练习来消化我一直在阅读的一些东西,我写了一个小函数来输出它所有参数类型的名称:

#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;
}

令我惊讶的是,使用 VC++ 12.0 编译并且(似乎)工作正常。

当只剩下一个参数时,由于重载之间的歧义,我预计会出现错误,因为我读到模板参数包可以是 empty/contain 0 个参数。

所以我的问题是为什么这样做有效? "potential ambiguity" 是如何解决的?这两个函数的签名是否仅与 1 个 arg 不相同?我觉得我可能在某处遗漏了一些重要的概念,因为在我看来上面的例子不应该编译,但显然我错了。

亲切的问候:)

您的代码有问题,但原因不同。如 answer to the related question Ambiguous call when recursively calling variadic template function overload 中所述,您的代码中的第二个重载被认为更专业。但是,它应该出现在带有参数包的例程之前。使用 gcc 8.2.1 或 clang 6.0.1 编译代码会出现类似

的错误
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()

正如您从错误中看到的那样,即使 get_arg_types 具有单个参数,编译器也会选择第一个重载,然后调用不带参数的 get_arg_types。一个简单的解决方案是将get_arg_types的单参数重载移到带有模板参数包的例程之前,或者在通用例程之前添加单参数get_arg_types的声明。

或者,您可以删除 get_arg_types 的专业化单参数并添加具有 0 个参数的专业化:

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

同样,应该在模板例程之前放置(或至少声明)这样的特化。请注意,对于第二种解决方案,输出略有变化。