可变参数模板和类型推断问题

Variadic template and type deduction issue

本文关键字:类型 问题 变参 参数      更新时间:2023-10-16

在2012年ACCU C++ Pub测验的第15题中,我对结果感到困惑。

#include <iostream>
template<typename T> void P(T x) { std::cout << x; }
void foo(char a) {  // foo 1
    P(3);
    P(a);
}
template <typename... A>  // foo 2
void foo(int a, A... args) {
    foo(args...);
    P(a);
}
template <typename... A>
void foo(char a, A... args) { // foo 3
    P(a);
    foo(args...);
}
int main()
{
    foo('1','2',48,'4','5');
}

我推断它会调用foo 3foo 3foo 2foo 3foo 1,从而给出1243548的输出。实际输出是12355248,并在调试器中确认如下foo 3foo 3foo 2foo 2foo 1。我无法弄清楚为什么第四次foo电话会foo 2foo 3.

作为参考,我使用 gcc 4.8.1 g++ -g -Wall -std=c++11 -Weffc++ -Wextra -O0 /tmp/foo.cpp -o /tmp/foo编译,根本没有收到任何警告。


编辑:我刚刚在Visual Studio Express 2013上尝试过,它给出了1243548,也没有警告。

这是GCC/VS中的编译器错误,还是规范中那些尴尬的未指定行为部分之一?

看起来这是声明的顺序。如果你在 foo 2 上方转发声明 foo 的相关重载,那么你将看到你预期的结果,即将这个放在 foo 2 之上:

template <typename... A>
void foo(char a, A... args);

该标准的相关部分在3.4.1.4中:

在任何

函数、类或 用户声明的命名空间,应在全局使用之前声明 范围。

在 14.6.4.1 依赖名称解析中:

在解析从属名称时,来自以下源的名称是 考虑:

— 在定义点可见的声明 的模板。

— 来自与 实例化上下文中的函数参数的类型 (14.6.4.1)和定义上下文。

由于args是依赖类型,因此名称解析仅将可见的名称视为模板的定义点。 foo 3 此时尚未声明,因此不能在过载解决中考虑。在此基础上,Visual Studio在允许使用foo 3方面似乎是错误的。

foo 2无法调用foo 3 foo 3因为它不在foo 2范围内。