Подтвердить что ты не робот

Почему `<< std:: endl` не вызывает оператор, который я хочу ему позвонить?

Я искал решение для записи в файл и консоль одновременно. Я нашел приятное решение здесь.

Поскольку я работаю с pre С++ 11, мне пришлось внести небольшое изменение в код из 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;
};

Он отлично работает, кроме маленькой вещи, которая меня озадачивает. Если я использую его так:

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;
}

то все теги std::endl игнорируются для вывода на консоли, пока они отображаются правильно в файле. Я предполагаю, что когда я использую std::endl, вызывается ostream::operator<<, который будет печатать в файл, но не на консоль. Строкой с static_cast<OutputAndConsole&> является моя дилетантская попытка вызвать правильный оператор, но на консоли появляется только разрыв строки \n.

Почему для std::endl вызывается неправильный оператор?

Как я могу назвать правильный?

PS: я знаю, что без проблем могу использовать \n, но все же мне хотелось бы знать, что здесь происходит и как его исправить.

4b9b3361

Ответ 1

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

template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);

Как отмечали другие, std::endl является функцией шаблона. Функция шаблона не является значением, это просто имя.

Функция шаблона может быть преобразована в значение, если вы попытаетесь передать ее функции, ожидающей функции совместимой сигнатуры. Он не преобразуется в значение, если передается функции шаблона, принимающей T или const T&, потому что имя функции шаблона представляет целый ряд возможных значений.

Поскольку std::endl не является допустимым аргументом для вашего пользовательского operator<<, он выглядит в другом месте. Он находит std::ofstream operator<<, который выполняет функцию 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;
};

то две перегрузки <<:

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;
}

который вызывает шаблон std::endl для поиска соответствия <<.

Ответ 2

Попробуем что-нибудь более простое:

#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;
}

Это не скомпилируется:

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’

Почему? Что компилятор пытается нам рассказать?

Определение 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 );

Итак, std::endl не является переменной. Это шаблон. Точнее, шаблонная функция. В моем маленьком коде компилятор не может создать экземпляр шаблона.

Когда мы вызываем непосредственно std::cout << std::endl;, компилятор создает экземпляр std::endl из CharT и Traits из decltype(std::cout).

В вашем коде компилятор вместо этого создает шаблон с помощью CharT и Traits из std::ofstream, потому что ваш OutputAndConsole является потомком std::ofstream. Когда std::cout пытается вывести неверный экземпляр std::endl, он терпит неудачу.

PS: Последний абзац является лишь частично правильным. Когда вы пишете

oac << something;

где something имеет тип T,

Теоретически он может вызвать любое из двух

std::ofstream& std::ofstream::operator<<(T)  // or operator<<(const T&)
// -- or --
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);

Первое определение возможно, потому что OutputAndConsole наследует функцию-член operator<< от std::ofstream. Вторая форма предоставляется вами.

Когда something является переменной, она использует второе определение.

Когда something является шаблоном, он не может использовать второе определение, так как нет способа определить параметры шаблона. Поэтому он использует первое определение. Следовательно,

oac << std::endl;  // std::endl is a template

эквивалентно

static_cast<ofstream&>(oac) << std::endl;

Мы можем видеть это по следующему коду:

#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;
}

Этот код НЕ печатает "X".

Ответ 3

std::endl - это функция, а не строка. Ваш перегруженный метод берет строку для перегрузки, поэтому это не тот, кто получает вызов, когда вы делаете << std::endl

Вам нужно создать оператора, который выполняет функцию, которая имеет ту же подпись, что и std:endl, чтобы сделать вашу перегрузку.

 std::ostream& operator<<( std::ostream& (*f)(std::ostream&) )

Ответ 4

Я предлагаю не реализовывать стандартные потоки ввода-вывода через потоковый интерфейс, а интерфейс streambuffer. Пользовательский потоковый интерфейс обычно вызывает проблемы, как только поток распознается только как std:: istream/std:: ostream (из-за того, что какой-либо оператор, манипулятор или функция ссылаются на поток).

Вы можете использовать:

#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");
}

Обратите внимание: единственная функция, применяющая изменения в интерфейсе std:: basic_streambuf, - это virtual int sync() override.

Стандартные базовые потоковые классы не предоставляют никакого интерфейса, кроме выведения и инициализации настраиваемого класса потока. Фактический (виртуальный) интерфейс является стандартным буфером потока basic_streambuf.

Ответ 5

Чтобы он работал, я создал свой собственный набор манипуляторов:

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;
}