"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
的引用的歧义。但是管理该程序的规则是什么?看起来重载解析是一个真正的挑战,它必须同时回答两个问题:
std::endl
指的是std::endl<CharT, Traits>()
的哪个专业?
operator<<
指的是哪个函数?
模板参数推导 (1) 应该在重载决议 (2) 之前发生,但似乎需要执行 (2) (至少部分) 才能使 (1) 成功。
有些相关但绝不会重复的问题是:
- Does std::endl work with both cout and wcout?
- How does std::flush work?
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
类型的信息,它知道 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
,它就不能推断出该函数的 return 类型,因此类型推导失败。
大多数 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
的引用的歧义。但是管理该程序的规则是什么?看起来重载解析是一个真正的挑战,它必须同时回答两个问题:
std::endl
指的是std::endl<CharT, Traits>()
的哪个专业?operator<<
指的是哪个函数?
模板参数推导 (1) 应该在重载决议 (2) 之前发生,但似乎需要执行 (2) (至少部分) 才能使 (1) 成功。
有些相关但绝不会重复的问题是:
- Does std::endl work with both cout and wcout?
- How does std::flush work?
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
类型的信息,它知道 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
,它就不能推断出该函数的 return 类型,因此类型推导失败。