递归可变参数模板功能-不明确吗?

时间:2018-11-23 11:26:59

标签: c++ templates variadic-templates variadic-functions

我目前正在研究可变参数模板,并且作为一个摘要练习来消化我一直在阅读的某些内容,我编写了一个小函数来输出其所有参数类型的名称:

#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进行了编译,并且(似乎)可以正常工作。

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

所以我的问题是为什么这行得通?如何解决“潜在歧义”?两个函数的签名是否仅包含1个arg不一致?我觉得我可能在某个地方错过了一些重要的概念,因为在我看来,上面的示例不应该编译,但是显然我错了。

亲切的问候:)

1 个答案:

答案 0 :(得分:1)

您的代码有问题,但原因有所不同。如相关问题answerAmbiguous 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();
}

同样,应在模板例程之前放置(或至少声明)这样的专业化名称。请注意,在第二种解决方案中,输出会稍有变化。