大多数IO流操纵符都是正则函数,具有以下签名:
std::ios_base& func( std::ios_base& str );
然而,一些操纵符(包括最常用的std::endl
和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
的引用的歧义。但是控制这个过程的规则是什么呢?这看起来对重载解析是一个真正的挑战,它必须同时回答两个问题:
-
std::endl
指的是std::endl<CharT, Traits>()
的哪个专门化? -
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
的类型信息,因此它知道charT
和traits
是什么,以便对前面的模板进行专门化。
以上导致表达式std::cout << std::endl
中的std::endl
匹配到特定的std::basic_ostream<charT, traits>& endl( std::basic_ostream<charT, traits>&)
。
类型演绎在
中不起作用的原因 auto myendl = std::endl;
是因为std::endl
是一个模板化的函数,并且此声明没有提供专门化该模板的信息(即选择charT
或traits
是什么)。如果它不能特化模板化的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行来跳过