为什么`<< std :: endl`没有调用我希望它调用的运算符?

for*_*818 28 c++ operator-overloading

我一直在寻找一种解决方案来同时写入文件和控制台.我发现了一个很好的解决方案在这里.

当我在C++ 11之前工作时,我不得不对Orbit中Lightness Races的代码进行一些小改动:

#include <iostream>
#include <fstream>
#include <string>

struct OutputAndConsole : std::ofstream
{
    OutputAndConsole(const std::string& fileName)
       : std::ofstream(fileName.c_str())   // constructor taking a string is C++11
       , fileName(fileName)
    {};

    const std::string fileName;
};

template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var)
{
    std::cout << var;    
    static_cast<std::ofstream&>(strm) << var;
    return strm;
};
Run Code Online (Sandbox Code Playgroud)

它很好地工作除了一个小东西需要困惑我.如果我像这样使用它:

int main(){
    OutputAndConsole oac("testLog.dat");
    double x = 5.0;
    oac << std::endl;
    static_cast<OutputAndConsole&>(oac << "foo \n" << x << "foo").operator<<(std::endl);  
    oac << "foo" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

然后,std::endl当它们在文件中正确显示时,控制台上的输出将忽略所有内容.我的猜测是,当我使用std::endlostream::operator<<被称为将打印到文件而不是控制台.与static_cast<OutputAndConsole&>我一起尝试调用正确的运算符,但仍然只有\n控制台上出现换行符.

为什么要std::endl调用错误的运算符?

我怎么称呼正确的?

PS:我知道我可以\n毫无问题地使用,但我仍然想知道这里发生了什么,以及如何解决它.

Yak*_*ont 12

struct OutputAndConsole : std::ofstream
{
  // ...
};

template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
Run Code Online (Sandbox Code Playgroud)

正如其他人所提到的,std::endl是模板功能.模板函数不是值,它只是一个名称.

如果您尝试将模板函数传递给期望兼容签名功能的函数,则可以将模板函数转换为值.这是不是如果传递给一个模板函数回吐转换为值Tconst T&,因为模板函数的名称代表了整个主机的可能值.

因为std::endl它不是您自定义编写的有效参数operator<<,所以它在其他地方查找.它发现std::ofstreamoperator<<,通过明确的函数指针需要一个IO控制器函数.

那一个工作,它可以转换endl为该函数指针类型!所以,兴高采烈地,它称之为.

要解决此问题,请添加一个operator<<重载OutputAndConsole,以获取io操纵器函数指针.

最简单的方法是编写辅助函数:

template <class T>
void output_to(OutputAndConsole& strm, const T& var)
{
  std::cout << var;    
  static_cast<std::ofstream&>(strm) << var;
};
Run Code Online (Sandbox Code Playgroud)

然后两个<<重载:

template<class T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var) {
  output_to(strm, var);
  return strm;
}
OutputAndConsole& operator<<(OutputAndConsole& strm, std::ostream& (*var)(std::ostream&)) {
  output_to(strm, var);
  return strm;
}
Run Code Online (Sandbox Code Playgroud)

这导致std::endl模板找到匹配<<.


use*_*264 11

让我们尝试一些更简单的方法:

#include <iostream>

struct Foo { };

template <typename T>
Foo& operator<<(Foo& foo, const T& var)
{
    std::cout << var;
    return foo;
};

int main(){
    Foo foo;
    foo << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

这不编译:

a1.cpp: In function ‘int main()’:
a1.cpp:14:9: error: no match for ‘operator<<’ (operand types are ‘Foo’ and ‘<unresolved overloaded function type>’)
     foo << std::endl;
         ^
a1.cpp:14:9: note: candidates are:
a1.cpp:6:6: note: template<class T> Foo& operator<<(Foo&, const T&)
 Foo& operator<<(Foo& foo, const T& var)
      ^
a1.cpp:6:6: note:   template argument deduction/substitution failed:
a1.cpp:14:17: note:   couldn't deduce template parameter ‘T’
Run Code Online (Sandbox Code Playgroud)

为什么?编译器试图告诉我们什么?

std :: endl的定义可以在这里找到:http: //en.cppreference.com/w/cpp/io/manip/endl

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );
Run Code Online (Sandbox Code Playgroud)

所以std::endl不是变量.这是一个模板.更准确地说,是模板功能.在我的小代码中,编译器无法实例化模板.

当我们直接调用std::cout << std::endl;,编译器实例化std::endlCharTTraitsdecltype(std::cout).

在您的代码中,编译器使用CharTTraits从实例化模板std::ofstream,因为您OutputAndConsole是.的后代std::ofstream.当std::cout尝试输出错误的实例时std::endl,它会失败.

PS:最后一段只是部分正确.当你写作

oac << something;
Run Code Online (Sandbox Code Playgroud)

哪里something有T型,

从理论上讲,它可以称为两个中的任何一个

std::ofstream& std::ofstream::operator<<(T)  // or operator<<(const T&)
// -- or --
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
Run Code Online (Sandbox Code Playgroud)

第一个定义是可能的,因为OutputAndConsole继承的成员函数operator<<std::ofstream.第二种形式由您提供.

何时something是变量,它使用第二个定义.

何时something是模板,它不能使用第二个定义,因为无法确定模板的参数.所以它使用了第一个定义.因此,

oac << std::endl;  // std::endl is a template
Run Code Online (Sandbox Code Playgroud)

相当于

static_cast<ofstream&>(oac) << std::endl;
Run Code Online (Sandbox Code Playgroud)

我们可以通过以下代码看到它:

#include <iostream>

struct Foo : std::ofstream {};

template <typename T>
Foo& operator<<(Foo& strm, const T& var)
{
    std::cout << "X" << std::endl;
    return strm;
};

int main() {
    Foo oac;
    oac << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

此代码不打印"X".


Mek*_*kap 6

std::endl是一个函数,而不是一个字符串.你的重载方法需要一个字符串来进行重载,所以当你这样做的时候不会被调用<< std::endl

您需要创建一个运算符,该运算符采用具有相同签名的函数std:endl来执行重载.

 std::ostream& operator<<( std::ostream& (*f)(std::ostream&) )
Run Code Online (Sandbox Code Playgroud)