"std::cout << std::endl;" 如何编译?

How does "std::cout << std::endl;" compile?

大多数 IO stream manipulators 是具有以下签名的常规函数​​:

std::ios_base& func( std::ios_base& str );

但是一些操纵器(包括最常用的 - std::endl and std::flush)是以下形式的模板:

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& func(std::basic_ostream<CharT, Traits>& os);

那么,std::cout << std::endl;编译失败,下面的例子是如何编译成功的:

$ cat main.cpp 
#include <iostream>

int main()
{
    auto myendl = std::endl;
    std::cout << myendl;
}

$ g++ -std=c++11    main.cpp   -o main
main.cpp: In function ‘int main()’:
main.cpp:5:24: error: unable to deduce ‘auto’ from ‘std::endl’
     auto myendl = std::endl;
                        ^

很明显,上下文(在 std::cout << std::endl; 中)有助于编译器消除对 std::endl 的引用的歧义。但是管理该程序的规则是什么?看起来重载解析是一个真正的挑战,它必须同时回答两个问题:

  1. std::endl指的是std::endl<CharT, Traits>()的哪个专业?
  2. operator<<指的是哪个函数?

模板参数推导 (1) 应该在重载决议 (2) 之前发生,但似乎需要执行 (2) (至少部分) 才能使 (1) 成功。


有些相关但绝不会重复的问题是:

None 这些问题和它们的答案都没有解决模板参数推导的工作原理,该推导应该先于重载解决方案,但必须得到后者的帮助。


后续问题:

因为 basic_ostream 有一个 operator<< 的模板化重载,它需要这样一个函数指针:

basic_ostream<charT, traits>& operator<<(basic_ios<charT, traits>& (*pf)(basic_ios<charT, traits>&));

有问题的operator<<std::basic_ostream成员:

namespace std {
    template <class charT, class traits = char_traits<charT> >
    class basic_ostream {
    public:
        basic_ostream<charT,traits>& operator<<(
          basic_ostream<charT,traits>& (*pf)(basic_ostream<charT,traits>&));
        // ...
    };
}

由于调用是 std::cout << std::endl,或者等同于 std::cout.operator<<(std::endl),我们已经知道 basic_ostream 的确切实例化:std::basic_ostream<char, std::char_traits<char>>,又名 std::ostream。所以 cout 的成员函数看起来像

std::ostream& operator<<(std::basic_ostream<char, std::char_traits<char>>& (*pf)
    (std::basic_ostream<char, std::char_traits<char>>&));

该成员函数不是函数模板,只是一个普通的成员函数。所以剩下的问题是,是否可以使用名称 std::endl 作为参数来调用它?是的,初始化函数参数相当于一个变量初始化,就好像我们写了

std::basic_ostream<char, std::char_traits<char>>& (*pf)
    (std::basic_ostream<char, std::char_traits<char>>&) = std::endl;

你需要在endl之前加上<<。它是 ofstream 的成员:

namespace std {
template <class charT, class traits = char_traits<charT> >
class basic_ostream {
public:
    basic_ostream<charT,traits>& operator<<(
      basic_ostream<charT,traits>& (*pf)(basic_ostream<charT,traits>&));
    // ...
};

}

endl 的作用类似于“/n”来跳过该行,因此您需要一个 cout 行来跳过

给定形式的语句表达式

 std::cout << std::endl;

编译器有关于 std::cout 类型的信息——它是模板化 std::basic_ostream 的特化,看起来像(省略包含的 namespace std)。

template <class charT, class traits = char_traits<charT> >
    class basic_ostream
{
    public:
        basic_ostream<charT,traits>& operator<<(
            basic_ostream<charT,traits>& (*pf)(basic_ostream<charT,traits>&));

        // other members ....
};
   

因为编译器有关于 std::cout 类型的信息,它知道 charTtraits 是什么来专门化前面的模板。

以上导致表达式 std::cout << std::endl 中的 std::endl 匹配特定的 std::basic_ostream<charT, traits>& endl( std::basic_ostream<charT, traits>&)

类型推导在

中不起作用的原因
 auto myendl = std::endl;

是因为 std::endl 是一个模板函数,并且这个声明没有提供任何信息来专门化该模板(即选择 charTtraits 是什么)。如果它不能专门化模板化的 std::endl,它就不能推断出该函数的 return 类型,因此类型推导失败。