为什么我可以调用没有前向声明的函数模板?

时间:2013-09-07 14:34:54

标签: c++ function templates gcc forward-declaration

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

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)

3 个答案:

答案 0 :(得分:10)

这是一个“为什么用砖砌成的天空”式问题。即,一个问题,为什么一些错误是真的。在C ++中,您的代码不合法​​。

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

我想问题是“为什么gcc 4.6让这段代码编译”仍然存在。在编写template扩展器时编译器早期做的事情之一就是将它们视为与宏类似的东西。当他们声明的时候会做很少的事情,并且当他们实例化的时候,所有东西都会被抬起来。

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

实际上,ADL可以解决这个问题:通过ADL查找bar的{​​{1}}查找不必在写入bar的位置可见,而是在实例化点。

gcc 4.8错误消息非常自我解释:

foo

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

答案 1 :(得分:9)

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

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

您可能还想谷歌“两阶段查找”以获取更多信息。只有最新版本的GCC才能正确实现这些规则,因为一些检查也需要在解析模板的第一阶段完成。作为Yakk的指针,较新版本的GCC会拒绝您的代码,因此请始终检查GCC或Clang的最新版本是否安全。

答案 2 :(得分:4)

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

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

当您调用foo(42)时,编译必须生成(实例化)真实函数,并且此时它会查找之前无法找到的名称,找到您的栏模板(并触发它的实例化),一切都很顺利。

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