当模板函数按特定顺序放置时,代码无法编译

Ric*_*rdo 25 c++ templates variadic-templates

以下程序编译成功。

template<typename T>
T sum(T x) {
    return x;
}

template<typename T, typename... Args>
T sum(T x, Args... args) {
    return x + sum(args...);
    
}

int main() {
    sum(1, 2, 3, 4, 5);

}
Run Code Online (Sandbox Code Playgroud)

但是,当我切换模板函数的编写顺序时,它不再编译:

template<typename T, typename... Args>
T sum(T x, Args... args) {
    return x + sum(args...);

}

template<typename T>
T sum(T x) {
    return x;
}

int main() {
    sum(1, 2, 3, 4, 5);

}
Run Code Online (Sandbox Code Playgroud)

我得到的编译器错误是:

错误 C2780:“T sum(T,Args...)”:需要 2 个参数 - 提供 0 个,消息:请参阅“sum”的声明

为什么会出现这种情况?这两个函数在 main() 中调用之前不是已经定义了吗?为什么它们的书写顺序很重要?

Bri*_*ian 28

您问题的核心是以下问题:当函数名称(例如sum)出现在函数模板内时,它指的是哪个函数?

你写了:

这两个函数在 main() 中调用之前不是已经定义了吗?为什么它们的书写顺序很重要?

您似乎有以下期望:当实例化函数模板并且函数名称sum出现在其中时,编译器会发现sum迄今为止已声明的所有重载。因此,根据您的理论,由于sum函数模板在调用 in 之前不会被实例化main,此时, 的两个重载sum都是可见的,因此递归调用sum应该考虑这两个重载并最终选择一元重载来终止递归。

然而,正如您所看到的,情况并非如此。如果首先声明变量sum,它最终会使用单个参数递归调用自身(忽略sum稍后声明的一元重载),然后尝试调用sum()(无参数),从而导致编译错误。

sum因此,在此示例中,当编译器看到对函数模板内部的调用时,它只会查找sum已在调用点声明的函数。当你改变顺序时,你就使得可变参数sum永远不能调用一元sum。

但实际上,事实要复杂得多。C++ 标准需要一种通俗地称为“两阶段查找”的行为。这意味着:

  1. 当编译器看到函数模板的定义时,它会找到sum迄今为止已声明的所有名为的函数,并且
  2. 当编译器稍后实例化函数模板时,它使用依赖于参数的查找来查找其他重载,sum无论它们是在封闭模板之前还是之后声明的。

在您的代码中,sum仅使用类型为 的参数调用int,该类型不属于任何命名空间。因此,可变参数sum只发现自己处于第 1 阶段,而在第 2 阶段什么也没有。