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



大多数IO流操纵符都是正则函数,具有以下签名:

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

然而,一些操纵符(包括最常用的std::endlstd::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)成功。


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

  • std::endl可以同时使用cout和wcout吗?
  • 为什么endl(std::cout)编译
  • std::flush是如何工作的?

这些问题和答案都没有涉及模板实参推导的工作原理,这些推导应该在重载解析之前进行,但必须得到重载解析的帮助。


后续问题:当实参是重载函数时,重载解析如何工作?

所讨论的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;

给定一个形式为

的语句表达式
 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,它就不能推断该函数的返回类型,因此类型推断失败。

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

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

你需要把<<在结束之前。它是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行来跳过

相关内容

最新更新