简体   繁体   English

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

[英]Why can I call function templates without forward declarations?

If a normal function calls a function that has not been declared yet, I get a compile-time error: 如果普通函数调用尚未声明的函数,则会出现编译时错误:

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);
}

The fix is to either forward-declare the called function, or to switch the order of definitions. 修复方法是向前声明被调用的函数,或者切换定义的顺序。

However, these fixes do not seem to be necessary with function templates: 但是,功能模板似乎不需要这些修复:

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);
}

This compiles just fine. 编译得很好。 Why is that? 这是为什么? When the compiler sees bar(x) , why does it not complain? 当编译器看到bar(x) ,为什么不抱怨?

(I am using g++ 4.6.3) (我使用的是g ++ 4.6.3)

This is a "why is the sky made out of bricks" type question. 这是一个“为什么用砖砌成的天空”式问题。 Ie, a question that asks why something false is true. 即,一个问题,为什么一些错误是真的。 It is not the case that in C++ your code is legal. 事实并非如此,在C ++中您的代码是合法的。

Live example , as you can see in gcc 4.8 this does not actually compile. 实例 ,正如您在gcc 4.8中看到的那样,实际上并没有编译。

I guess the question "why does gcc 4.6 let this code compile" remains. 我想问题是“为什么gcc 4.6让这段代码编译”仍然存在。 One of the things that compilers did early on when writing template expanders was to treat them as something similar to macros. 编译器在编写template扩展器时早期做的事情之一是将它们视为类似于宏的东西。 Very little would be done when they where declared, and everything would be looked up when they where instantiated. 当声明它们时,它们会很少被完成,并且当它们被实例化时,所有内容都会被查找。

Compilers now tend to do more thing when the template is declared, and less when it is instantiated. 现在,编译器在声明template时会做更多的事情,而在实例化时则更少。 This is what the C++ standard requires, or is at least closer. 这就是C ++标准所要求的,或者至少更接近。

As it happens, ADL can get around this: bar lookups that find bar via ADL do not have to be visible at the point where foo is written, but rather at the point of instantiation. 碰巧的是,ADL可以解决这个问题:通过ADL查找bar bar查找不必在写入foo的位置可见,而是在实例化时。

The gcc 4.8 error message is pretty self explanatory: 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)
      ^

these requirements may have been changed or clarified in C++11, so it is possible that gcc 4.6's behavior was legal under the C++03 standard. 这些要求可能已在C ++ 11中进行了更改或澄清,因此gcc 4.6的行为可能在C ++ 03标准下是合法的。

When the compiler first sees bar(x) , it doesn't know x 's type, hence it can't look up the correct bar . 当编译器第一次看到bar(x) ,它不知道x的类型,因此它无法查找正确的bar Only when you instantiate foo , T and therefore x 's type are known and bar(x) can be looked up. 只有当你实例化foo ,才知道Tx的类型,并且可以查找bar(x)

Note that this work only for dependent expression, ie expressions that depend on a template parameter. 请注意,这仅适用于依赖表达式,即依赖于模板参数的表达式。 If you add bar(42) , it will fail to compile even if it is later instantiated with T==int . 如果添加bar(42) ,即使稍后使用T==int实例化,它也将无法编译。

You might also want to google "two-phase lookup" for further information. 您可能还想谷歌“两阶段查找”以获取更多信息。 Only recent versions of GCC implement those rules correctly, as some checks also need to be done during the first phase of parsing the template. 只有最新版本的GCC才能正确实现这些规则,因为在解析模板的第一阶段还需要进行一些检查。 As pointer out by Yakk, newer versions of GCC reject your code, so always check with up-to-date versions of GCC or Clang to be on the safe side. 作为Yakk的指针,较新版本的GCC会拒绝您的代码,因此请始终使用最新版本的GCC或Clang进行检查以确保安全。

A function template isn't a function; 功能模板不是功能; it's a recipe for making functions, once the template parameters are known. 一旦模板参数已知,它就是制作函数的秘诀。

The compiler can't look up what bar means when it sees the foo template definition, because what it means could depend on what T is. 编译器在看到foo模板定义时无法查找哪个bar意味着什么,因为它意味着什么可能取决于T是什么。 So it just remembers that there's a use of the name bar that will need to be worked out later. 所以它只记得有一个名称bar的使用需要稍后解决。

When you call foo(42) the compile has to produce ( instantiate ) the real function, and at that point it looks up the names it wasn't able to before, finds your bar template (and triggers instantiation of that too) and all is well. 当你调用foo(42) ,编译必须生成( 实例化 )真实函数,并且此时它会查找之前无法找到的名称,找到你的条形模板(并触发它的实例化)和所有很好。

For a normal function all names can be looked up when the function is defined, and so they must be properly declared at that point. 对于普通函数,在定义函数时可以查找所有名称,因此必须在该点正确声明它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM