[英]Why does `<< std::endl` not call the operator I want it to call?
I was looking for a solution to write to a file and the console at the same time.我正在寻找一种同时写入文件和控制台的解决方案。 I found a nice solution here .我在这里找到了一个不错的解决方案。
As I am working pre C++11 I had to make a small change to the code from Lightness Races in Orbit:当我在 C++11 之前工作时,我不得不对 Lightness Races in Orbit 中的代码进行一些小改动:
#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;
};
It works nicely apart from a small thing takes puzzles me.除了一件小事让我感到困惑之外,它工作得很好。 If I use it like this:如果我这样使用它:
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;
}
then all the std::endl
are ignored for the output on the console while they appear correctly in the file.然后控制台上的 output 的所有std::endl
都将被忽略,而它们在文件中正确显示。 My guess is that when I use std::endl
the ostream::operator<<
is called which will print to the file but not to the console.我的猜测是,当我使用std::endl
时,会调用ostream::operator<<
它将打印到文件而不是控制台。 The line with the static_cast<OutputAndConsole&>
is my dilettantish attempt to call the correct operator, but still only the line break from \n
appears on the console.带有static_cast<OutputAndConsole&>
的行是我尝试调用正确运算符的业余尝试,但控制台上仍然只有\n
的换行符。
Why for std::endl
the wrong operator is called?为什么std::endl
调用了错误的运算符?
How can I call the correct one?我怎样才能调用正确的?
PS : I know that I can use \n
without problems, but still I would like to know what is going on here and how to fix it. PS :我知道我可以毫无问题地使用\n
,但我仍然想知道这里发生了什么以及如何解决它。
struct OutputAndConsole : std::ofstream
{
// ...
};
template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
As others have mentioned, std::endl
is a template function. 正如其他人所提到的, std::endl
是一个模板函数。 A template function is not a value , it is just a name. 模板函数不是值 ,它只是一个名称。
A template function can be converted to a value if you try to pass it to a function expecting function of a compatible signature. 如果您尝试将模板函数传递给期望兼容签名功能的函数,则可以将模板函数转换为值。 It is not converted to a value if passed to a template function taking T
or const T&
, because the name of a template function represents a whole host of possible values. 如果传递给采用T
或const T&
的模板函数, 则不会将其转换为值,因为模板函数的名称表示可能值的整个主机 。
Because std::endl
is not a valid argument for your custom-written operator<<
, it looks elsewhere. 因为std::endl
不是自定义编写的operator<<
的有效参数,所以它在其他地方查找。 It finds the std::ofstream
's operator<<
that takes an io manipulator function by explicit function pointer. 它找到std::ofstream
的operator<<
,它通过显式函数指针获取io操纵器函数。
That one works, it can convert endl
to that function pointer type! 那个工作,它可以将endl
转换为该函数指针类型! So, gleefully, it calls it. 所以,兴高采烈地,它称之为。
To fix this problem, add an operator<<
overload to OutputAndConsole
that takes io manipulator function pointers. 要解决此问题,请向OutputAndConsole
io操纵器函数指针的OutputAndConsole
添加一个operator<<
overload。
The easiest way to do that is to write a helper function: 最简单的方法是编写辅助函数:
template <class T>
void output_to(OutputAndConsole& strm, const T& var)
{
std::cout << var;
static_cast<std::ofstream&>(strm) << var;
};
then two <<
overloads: 然后两个<<
重载:
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;
}
which causes the std::endl
template to find a matching <<
. 这导致std::endl
模板找到匹配的<<
。
Let us try something simpler: 让我们尝试一些更简单的方法:
#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;
}
This does not compile: 这不编译:
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’
Why? 为什么? What does the compiler try to tell us? 编译器试图告诉我们什么?
The definition of std::endl can be found here: http://en.cppreference.com/w/cpp/io/manip/endl 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 );
So std::endl
is not a variable. 所以std::endl
不是变量。 It is a template . 这是一个模板 。 More precisely, a template function. 更准确地说,是模板功能。 In my little code, the compiler is unable to instantiate the template. 在我的小代码中,编译器无法实例化模板。
When we directly call std::cout << std::endl;
当我们直接调用std::cout << std::endl;
, the compiler instantiates std::endl
from CharT
and Traits
of decltype(std::cout)
. 时,编译器实例化std::endl
从CharT
和Traits
的decltype(std::cout)
In your code, the compiler instead instantiates the template using CharT
and Traits
from std::ofstream
, because your OutputAndConsole
is a descendant of std::ofstream
.在您的代码中,编译器使用来自 std::ofstream
CharT
和Traits
来实例化模板,因为您的OutputAndConsole
是std::ofstream
的后代。When std::cout
tries to output the wrong instantiation of std::endl
, it fails.当 std::cout
尝试输出std::endl
的错误实例时,它会失败。
PS: The last paragraph is only partially correct. PS:最后一段只是部分正确。 When you write 当你写作
oac << something;
where something
has type T, something
有T型,
It can, theoretically, call either of two 从理论上讲,它可以称为两个中的任何一个
std::ofstream& std::ofstream::operator<<(T) // or operator<<(const T&)
// -- or --
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
The first definition is possible because OutputAndConsole
inherited the member function operator<<
from std::ofstream
. 第一个定义是可能的,因为OutputAndConsole
从std::ofstream
继承了成员函数operator<<
。 The second form is provided by you. 第二种形式由您提供。
When something
is a variable, it uses the second definition. 当something
是变量时,它使用第二个定义。
When something
is a template, it cannot use the second definition, as there is no way to determine the parameters of the template. 当something
是模板时,它不能使用第二个定义,因为无法确定模板的参数。 So it uses the first definition. 所以它使用了第一个定义。 Therefore, 因此,
oac << std::endl; // std::endl is a template
is equivalent to 相当于
static_cast<ofstream&>(oac) << std::endl;
We can see it by the following code: 我们可以通过以下代码看到它:
#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;
}
This code does NOT print "X". 此代码不打印“X”。
std::endl
is a function, not a string. std::endl
是一个函数,而不是一个字符串。 Your overloaded method takes a string for the overloading, so it isnt this one who get called when you do << std::endl
你的重载方法需要一个字符串来进行重载,所以当你执行<< std::endl
时,它不会被调用
You need to create an operator who takes a function who has the same signature as std:endl
to do your overload. 您需要创建一个运算符,该运算符接受与std:endl
具有相同签名的函数来执行重载。
std::ostream& operator<<( std::ostream& (*f)(std::ostream&) )
I suggest not to implement standard I/O stream functionality via the stream-interface, but the streambuffer-interface. 我建议不要通过流接口实现标准的I / O流功能,而是使用streambuffer接口。 A customized stream-interface leads usually to trouble as soon as the stream is recognized as a std::istream/std::ostream, only (due to some operator, manipulator or function taking a reference to a stream). 一旦流被识别为std :: istream / std :: ostream,定制的流接口通常会导致麻烦(由于某些操作员,操纵者或函数引用了流)。
You may utilize: 你可以利用:
#include <array>
#include <iostream>
#include <sstream>
#include <vector>
// BasicMultiStreamBuffer
// ============================================================================
/// A (string) stream buffer for synchronizing writes into multiple attached buffers.
template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStreamBuffer : public std::basic_stringbuf<Char, Traits, Allocator>
{
// Types
// =====
private:
typedef typename std::basic_stringbuf<Char, Traits> Base;
public:
typedef typename std::basic_streambuf<Char, Traits> buffer_type;
typedef typename buffer_type::char_type char_type;
typedef typename buffer_type::traits_type traits_type;
typedef typename buffer_type::int_type int_type;
typedef typename buffer_type::pos_type pos_type;
typedef typename buffer_type::off_type off_type;
private:
typedef typename std::vector<buffer_type*> container_type;
public:
typedef typename container_type::size_type size_type;
typedef typename container_type::value_type value_type;
typedef typename container_type::reference reference;
typedef typename container_type::const_reference const_reference;
typedef typename container_type::iterator iterator;
typedef typename container_type::const_iterator const_iterator;
// Construction/Destructiion
// =========================
public:
BasicMultiStreamBuffer()
{}
template <typename...Buffers>
BasicMultiStreamBuffer(Buffers* ...buffers) {
std::array<buffer_type*, sizeof...(Buffers)> buffer_array{buffers...};
m_buffers.reserve(buffer_array.size());
for(auto b : buffer_array) {
if(b)
m_buffers.push_back(b);
}
}
template <typename Iterator>
BasicMultiStreamBuffer(Iterator first, Iterator last)
: m_buffers(first, last)
{}
~BasicMultiStreamBuffer() {
sync();
}
private:
BasicMultiStreamBuffer(BasicMultiStreamBuffer const&); // No Copy.
BasicMultiStreamBuffer& operator=(BasicMultiStreamBuffer const&); // No Copy.
// Capacity
// ========
public:
bool empty() const { return m_buffers.empty(); }
size_type size() const { return m_buffers.size(); }
// Iterator
// ========
public:
iterator begin() { return m_buffers.begin(); }
const_iterator begin() const { return m_buffers.end(); }
iterator end() { return m_buffers.end(); }
const_iterator end() const { return m_buffers.end(); }
// Modifiers
// =========
public:
/// Attach a buffer.
void insert(buffer_type* buffer) {
if(buffer) m_buffers.push_back(buffer);
}
/// Synchronize and detach a buffer.
void erase(buffer_type* buffer) {
iterator pos = this->begin();
for( ; pos != this->end(); ++pos) {
if(*pos == buffer) {
char_type* p = this->pbase();
std::streamsize n = this->pptr() - p;
if(n)
sync_buffer(*pos, p, n);
m_buffers.erase(pos);
break;
}
}
}
// Synchronization
// ===============
private:
int sync_buffer(buffer_type* buffer, char_type* p, std::streamsize n) {
int result = 0;
std::streamoff offset = 0;
while(offset < n) {
int k = buffer->sputn(p + offset, n - offset);
if(0 <= k) offset += k;
else {
result = -1;
break;
}
if(buffer->pubsync() == -1)
result = -1;
}
return result;
}
protected:
/// Synchronize with the attached buffers.
/// \ATTENTION If an attached buffer fails to synchronize, it gets detached.
virtual int sync() override {
int result = 0;
if( ! m_buffers.empty()) {
char_type* p = this->pbase();
std::streamsize n = this->pptr() - p;
if(n) {
iterator pos = m_buffers.begin();
while(pos != m_buffers.end()) {
if(0 <= sync_buffer(*pos, p, n)) ++pos;
else {
pos = m_buffers.erase(pos);
result = -1;
}
}
}
}
this->setp(this->pbase(), this->epptr());
if(Base::sync() == -1)
result = -1;
return result;
}
private:
container_type m_buffers;
};
typedef BasicMultiStreamBuffer<char> OStreamBuffers;
// BasicMultiStream
// ============================================================================
template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStream : public std::basic_ostream<Char, Traits>
{
// Types
// =====
private:
typedef std::basic_ostream<Char, Traits> Base;
public:
typedef BasicMultiStreamBuffer<Char, Traits, Allocator> multi_buffer;
typedef std::basic_ostream<Char, Traits> stream_type;
typedef typename multi_buffer::buffer_type buffer_type;
typedef typename multi_buffer::char_type char_type;
typedef typename multi_buffer::traits_type traits_type;
typedef typename multi_buffer::int_type int_type;
typedef typename multi_buffer::pos_type pos_type;
typedef typename multi_buffer::off_type off_type;
typedef typename multi_buffer::size_type size_type;
typedef typename multi_buffer::value_type value_type;
typedef typename multi_buffer::reference reference;
typedef typename multi_buffer::const_reference const_reference;
typedef typename multi_buffer::iterator iterator;
typedef typename multi_buffer::const_iterator const_iterator;
// Construction
// ============
public:
BasicMultiStream()
: Base(&m_buffer)
{}
template <typename ...Streams>
BasicMultiStream(Streams& ...streams)
: Base(&m_buffer), m_buffer(streams.rdbuf()...)
{}
private:
BasicMultiStream(const BasicMultiStream&); // No copy.
const BasicMultiStream& operator = (const BasicMultiStream&); // No copy.
// Capacity
// ========
public:
bool empty() const { return m_buffer.empty(); }
size_type size() const { return m_buffer.size(); }
// Iterator
// ========
public:
iterator begin() { return m_buffer.begin(); }
const_iterator begin() const { return m_buffer.end(); }
iterator end() { return m_buffer.end(); }
const_iterator end() const { return m_buffer.end(); }
// Modifiers
// =========
public:
template <typename StreamIterator>
void insert(StreamIterator& first, StreamIterator& last)
{
while(first != last)
insert(*first++);
}
void insert(stream_type& stream) { m_buffer.insert(stream.rdbuf()); }
void erase(stream_type& stream) { m_buffer.erase(stream.rdbuf()); }
private:
multi_buffer m_buffer;
};
typedef BasicMultiStream<char> MultiStream;
int main() {
MultiStream s(std::cout, std::cerr, std::clog);
s << "Hello World" << std::endl;
printf("[Three lines of output]\n");
}
Note, the only function applying changes to the std:: basic_streambuf interface is virtual int sync() override
. 注意,应用更改到std :: basic_streambuf接口的唯一函数是virtual int sync() override
。
The standard basic stream classes do not provide any interface besides deriving and initializing a custom stream class. 除了派生和初始化自定义流类之外,标准基本流类不提供任何接口。 The actual (virtual) interface is the standard stream buffer basic_streambuf
. 实际(虚拟)接口是标准流缓冲区basic_streambuf
。
To make it work, I would create my own set of manipulators: 为了使它工作,我会创建自己的一组操纵器:
struct ManipEndl {};
const ManipEndl manip_endl;
OutputAndConsole& operator<<(OutputAndConsole& strm, const ManipEndl& foo)
{
std::cout << std::endl;
static_cast<std::ofstream&>(strm) << '\n'; // no need for std::endl here
return strm;
};
int main(){
OutputAndConsole oac("testLog.dat");
double x = 5.0;
oac << manip_endl;
oac << x << manip_endl << "foo" << manip_endl;
oac << "foo" << manip_endl;
}
I had a similar problem, and fixed it by making my operator<<
function a friend like this:我有一个类似的问题,并通过让我的operator<<
function 成为这样的朋友来解决它:
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>
friend OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var)
{
std::cout << var;
std::ofstream::operator << (var); //*See note at end
return strm;
}
};
* This is the preferred syntax for calling a base class method/operator from a derived class method/operator. * 这是从派生的 class 方法/运算符调用基本 class 方法/运算符的首选语法。 While your syntax will work in most cases, it would fail if std::ofstream operator <<
were declared pure-virtual in ofstream
.虽然您的语法在大多数情况下都可以工作,但如果std::ofstream operator <<
在ofstream
中声明为纯虚拟,它将失败。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.