为什么使用 endl(std::cout) 可以编译通过?

13

令人惊讶的是,以下代码在各种编译器和版本上都可以编译和运行而没有错误。

#include <iostream>

int main() {
    endl(std::cout);
    return 0;
}

Ideone链接

它是如何编译的?我确定在全局范围内没有 endl,因为像这样的代码:

std::cout << endl;

如果不使用using或者你需要std::endl,否则会失败。


类似于名称空间与编译器的有趣行为 - Shafik Yaghmour
2个回答

24

这种行为被称为参数依赖查找或Koenig查找。该算法告诉编译器在查找未限定的函数调用时不仅要查看本地作用域,还要查看包含参数类型的命名空间。

例如:

namespace foo {
  struct bar{
    int a;
  };

  void baz(struct bar) {
    ...
  }
};

int main() {
  foo::bar b = {42};
  baz(b);  // Also look in foo namespace (foo::baz)
  // because type of argument(b) is in namespace foo
}

关于问题中涉及的代码:

endlstd::endlstd 命名空间中声明,声明如下(链接)

template< class CharT, class Traits >
std::basic_ostream<charT,traits>&     endl( std::basic_ostream<CharT, Traits>& os );
或者
std::ostream& endl (std::ostream& os);

cout 或者 std::cout 被声明为 ...

extern std::ostream cout;

调用std::coutstd::endl是完全可以的。

当只调用endl(std::cout)时,由于参数cout的类型来自于std命名空间,未经限定的假定函数endl会在std命名空间中搜索,并成功地找到了它,确认它是一个函数,因此会调用合格的函数std::endl


进一步阅读:

  1. GOTW 30: 名称查找

  2. 为什么在语句“std::cout<< std::endl”中使用参数相关查找时,“std::endl”需要命名空间限定?


3
这是流操作符的工作方式。 操作符是作为参数传递给 << 运算符的函数。然后在运算符内部,它们被简单地调用。
因此,您可以声明一个函数,例如:
template <class charT, class traits>
basic_ostream<charT,traits>& endl(basic_ostream<charT,traits>& os);

并且您将其指针传递给运算符 <<。在声明了类似以下内容的运算符内部
ostream& ostream::operator << ( ostream& (*op)(ostream&));

该函数被称为“like”。

return (*endl )(*this);

因此,当您看到“record”时,
std::cout << std::endl;

那么,std::endl 是函数指针,作为参数传递给 operator <<

在记录中

std::endl( std::cout );

名称前的命名空间前缀endl可以省略,因为在这种情况下编译器将使用参数相关的查找。因此,这条记录可以这样写:

endl( std::cout );

成功编译。

但是,如果将函数名括在括号中,则不会使用ADL,并显示以下记录

( endl )( std::cout );

无法编译。


1
我认为这个问题是关于为什么在函数调用形式上不需要使用std::的原因。 - M.M
@Matt McNabb 我重新阅读了帖子,似乎你是正确的。:) - Vlad from Moscow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接