Поток С++ в качестве параметра при перегрузке оператора <<

Я пытаюсь написать свой собственный класс ведения журнала и использовать его как поток:

logger L;
L << "whatever" << std::endl;

Это код, который я начал с:

#include <iostream>

using namespace std;


class logger{
public:
    template <typename T>
    friend logger& operator <<(logger& log, const T& value);
};

template <typename T>
logger& operator <<(logger& log, T const & value) {
    // Here I'd output the values to a file and stdout, etc.
    cout << value;
    return log;
}

int main(int argc, char *argv[])
{
    logger L;
    L << "hello" << '\n' ; // This works
    L << "bye" << "alo" << endl; // This doesn't work
    return 0;
}

Но я получал ошибку при попытке скомпилировать, говоря, что не было определения для оператора < < (при использовании std:: endl):

pruebaLog.cpp:31: error: no match for ‘operator<<’ in ‘operator<< [with T = char [4]](((logger&)((logger*)operator<< [with T = char [4]](((logger&)(& L)), ((const char (&)[4])"bye")))), ((const char (&)[4])"alo")) << std::endl’

Итак, я пытался перегрузить оператор < < принять подобные потоки, но это сводит меня с ума. Я не знаю, как это сделать. Я, например, рассматривал определение std:: endl в заголовочном файле ostream и записывал функцию с этим заголовком:

logger& operator <<(logger& log, const basic_ostream<char,char_traits<char> >& (*s)(basic_ostream<char,char_traits<char> >&))

Но не повезло. Я пробовал использовать шаблоны вместо прямого использования char, а также попробовал просто использовать "const ostream & os" и ничего.

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

Ответ 1

endl - странный зверь. Это не постоянное значение. Это на самом деле, из всех вещей, функция. Для обработки приложения endl требуется специальное переопределение:

logger& operator<< (logger& log, ostream& (*pf) (ostream&))
{
  cout << pf;
  return log;
}

Это принимает вставку функции, которая принимает ссылку ostream и возвращает ссылку ostream. Это endl.

Изменить: В ответ на интересный вопрос FranticPedantic о том, почему компилятор не может это сделать автоматически? Причина в том, что если вы еще глубже погружаетесь, endl фактически является функцией шаблона. Он определяется как:

template <class charT, class traits>
  basic_ostream<charT,traits>& endl ( basic_ostream<charT,traits>& os );

То есть, он может принимать любой тип ostream в качестве своего ввода и вывода. Поэтому проблема заключается не в том, что компилятор не может определить, что T const & может быть указателем на функцию, но он не может определить, к какому endl вы должны были перейти. Шаблонная версия operator<<, представленная в вопрос будет принимать указатель на любую функцию в качестве второго аргумента, но в то же время шаблон endl представляет бесконечный набор потенциальных функций, поэтому компилятор не может там ничего значимого.

Предоставление специальной перегрузки operator<<, второй аргумент которой соответствует конкретному экземпляру шаблона endl, позволяет разрешить вызов.

Ответ 2

endl - это манипулятор IO, который является функтором, который принимает поток по ссылке, выполняет некоторую операцию над ним и возвращает этот поток также по ссылке. cout << endl эквивалентен cout << '\n' << flush, где flush - это манипулятор, который сбрасывает выходной буфер.

В вашем классе вам просто нужно написать перегрузку для этого оператора:

logger& operator<<(logger&(*function)(logger&)) {
    return function(*this);
}

Где logger&(*)(logger&) - это тип функции, принимающей и возвращающий logger по ссылке. Чтобы написать свои собственные манипуляторы, просто напишите функцию, которая соответствует этой сигнатуре, и попросите ее выполнить некоторую операцию в потоке:

logger& newline(logger& L) {
    return L << '\n';
}

Ответ 3

В С++ это буфер потока, который инкапсулирует базовый механизм ввода-вывода. Сам поток только инкапсулирует преобразования в строку и направление ввода/вывода.

Таким образом, вы должны использовать один из предопределенных классов потоков, а не создавать свои собственные. Если у вас есть новая цель, к которой вы хотите, чтобы ваш ввод-вывод выполнялся (например, системный журнал), то вы должны создавать собственный буфер потока (полученный от std::streambuf).