【问题标题】:How does "std::cout << std::endl;" compile?“std::cout << std::endl;”如何编译?
【发布时间】:2017-05-13 14:37:57
【问题描述】:

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

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 &lt;&lt; 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 &lt;&lt; std::endl; 中)有助于编译器消除对std::endl 的引用的歧义。但是管理该程序的规则是什么?重载决议看起来像是一个真正的挑战,它必须同时回答两个问题:

  1. std::endl&lt;CharT, Traits&gt;() 是指std::endl 的哪个专业化?
  2. operator&lt;&lt; 指的是哪个函数?

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


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

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


后续问题: How does overload resolution work when an argument is an overloaded function?

【问题讨论】:

  • @πάνταῥεῖ 这个问题不是重复的。 Dietmar Kühl 的回答甚至没有包含我在非常不同的问题中要求的信息(它只声明“编译器自动推断模板参数”,而我很好奇该推断是如何工作的)。
  • @Leon 您能否在您的问题中提出您的论点,为什么您认为在 Dietmar 或其他答案中没有明确解释?我可能会考虑重新提出您的问题。
  • @πάνταῥεῖ 我已经在我的编辑中添加了这些论点(在问题结束之前)。
  • 好吧,询问时以minimal reproducible example 开头。

标签: c++ templates overload-resolution iomanip


【解决方案1】:

有问题的operator&lt;&lt;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 &lt;&lt; std::endl,或者等效的std::cout.operator&lt;&lt;(std::endl),我们已经知道basic_ostream的确切实例化:std::basic_ostream&lt;char, std::char_traits&lt;char&gt;&gt;,又名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;

【讨论】:

【解决方案2】:

给定一个语句表达式的形式

 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 &lt;&lt; std::endl中的std::endl匹配到特定的std::basic_ostream&lt;charT, traits&gt;&amp; endl( std::basic_ostream&lt;charT, traits&gt;&amp;)

原因类型推导不起作用

 auto myendl = std::endl;

是因为std::endl 是一个模板化函数,并且此声明没有提供专门化该模板的信息(即选择charTtraits 是什么)。如果不能特化模板化的std::endl,就无法推断出该函数的返回类型,所以类型推导失败。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 在这种情况下重载解析是如何工作的(请参阅更新的问题)?
    【解决方案4】:

    你需要把

    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 行来跳过

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-19
      • 1970-01-01
      • 2012-01-08
      • 2021-08-04
      • 1970-01-01
      • 2020-07-23
      • 1970-01-01
      相关资源
      最近更新 更多