4

如果普通函数调用尚未声明的函数,则会出现编译时错误:

void foo(int x)
{
    bar(x);   // ERROR: bar has not been declared yet
}

void bar(int x)
{
    std::cout << x << '\n';
}

int main()
{
    foo(42);
}

解决方法是前向声明被调用的函数,或者切换定义的顺序。

但是,函数模板似乎不需要这些修复:

template<typename T>
void foo(T x)
{
    bar(x);   // OKAY
}

template<typename T>
void bar(T x)
{
    std::cout << x << '\n';
}

int main()
{
    foo(42);
}

这编译得很好。这是为什么?当编译器看到bar(x)时,它为什么不抱怨?

(我正在使用 g++ 4.6.3)

4

3 回答 3

10

这是一个“为什么天空是砖砌成的”类型的问题。即,一个问题,问为什么假的东西是真的。在 C++ 中,您的代码并非合法。

Live example,正如您在 gcc 4.8 中看到的那样,它实际上并没有编译。

我想“为什么 gcc 4.6 让这段代码编译”的问题仍然存在。编译器在编写template扩展器时所做的一件事就是将它们视为类似于宏的东西。当它们被声明时几乎不会做任何事情,当它们被实例化时,一切都会被查找。

编译器现在倾向于在template声明时做更多的事情,而在实例化时做更少的事情。这是 C++ 标准所要求的,或者至少更接近。

碰巧,ADL 可以解决这个问题:通过 ADLbar查找的查找bar不必在foo写入时可见,而是在实例化时可见。

gcc 4.8 错误消息非常不言自明:

prog.cpp: In instantiation of ‘void foo(T) [with T = int]’:
prog.cpp:16:7:   required from here
prog.cpp:6:10: error: ‘bar’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
     bar(x);   // OKAY
          ^
prog.cpp:10:6: note: ‘template<class T> void bar(T)’ declared here, later in the translation unit
 void bar(T x)
      ^

这些要求可能在 C++11 中已更改或澄清,因此 gcc 4.6 的行为可能在 C++03 标准下是合法的。

于 2013-09-07T14:46:33.743 回答
9

当编译器第一次看到bar(x)时,它不知道x的类型,因此它无法查找正确的bar。只有当您实例化foo,T因此x的类型是已知的并且bar(x)可以查找时。

请注意,这仅适用于依赖表达式,即依赖于模板参数的表达式。如果添加bar(42),即使稍后实例化它也将无法编译T==int

您可能还想谷歌“两阶段查找”以获取更多信息。只有最新版本的 GCC 才能正确实现这些规则,因为在解析模板的第一阶段还需要进行一些检查。正如 Yakk 指出的那样,较新版本的 GCC 会拒绝您的代码,因此请务必检查最新版本的 GCC 或 Clang 以确保安全。

于 2013-09-07T14:45:49.053 回答
4

函数模板不是函数;一旦知道模板参数,它就是制作函数的秘诀。

bar编译器在看到模板定义时无法查找含义foo,因为它的含义可能取决于 T 是什么。所以它只记得有一个名称的使用bar需要稍后解决。

当您调用foo(42)compile 时,必须生成(实例化)真正的函数,然后它会查找以前无法查找的名称,找到您的 bar 模板(并触发该模板的实例化),一切都很好。

对于普通函数,在定义函数时可以查找所有名称,因此必须在此时正确声明它们。

于 2013-09-07T14:43:40.190 回答