简体   繁体   English

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

[英]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. 如果传递给采用Tconst 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::ofstreamoperator<< ,它通过显式函数指针获取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::endlCharTTraitsdecltype(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 CharTTraits来实例化模板,因为您的OutputAndConsolestd::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 . 第一个定义是可能的,因为OutputAndConsolestd::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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM