overload operator c++ operator-overloading

overload - operator[] c++



¿Por qué `<< std:: endl` no llama al operador al que quiero que llame? (5)

Estaba buscando una solución para escribir en un archivo y la consola al mismo tiempo. He encontrado una buena solución here .

Mientras trabajaba antes de C ++ 11, tuve que hacer un pequeño cambio en el código de 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; };

Funciona muy bien aparte de que una pequeña cosa me lleva desconcertados. Si lo uso así:

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

entonces todos los std::endl se ignoran para la salida en la consola mientras aparecen correctamente en el archivo. Supongo que cuando uso std::endl al ostream::operator<< que imprimirá en el archivo pero no en la consola. La línea con el static_cast<OutputAndConsole&> es mi intento static_cast<OutputAndConsole&> de llamar al operador correcto, pero aún así solo aparece el salto de línea de /n en la consola.

¿Por qué para std::endl se llama al operador incorrecto?

¿Cómo puedo llamar al correcto?

PD : Sé que puedo usar /n sin problemas, pero aún así me gustaría saber qué está pasando aquí y cómo solucionarlo.


Intentemos algo más simple:

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

Esto no se compila:

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’

¿Por qué? ¿Qué trata de decirnos el compilador?

La definición de std :: endl se puede encontrar aquí: 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 );

Entonces std::endl no es una variable. Es una plantilla . Más precisamente, una función de plantilla. En mi pequeño código, el compilador no puede crear una instancia de la plantilla.

Cuando llamamos directamente a std::cout << std::endl; , el compilador std::endl instancia de std::endl de CharT y Traits de decltype(std::cout) .

En su código, el compilador en cambio CharT instancia de la plantilla utilizando CharT y Traits de std::ofstream , porque su OutputAndConsole es un descendiente de std::ofstream . Cuando std::cout intenta generar la instanciación incorrecta de std::endl , falla.

PS: El último párrafo es solo parcialmente correcto. Cuando escribes

oac << something;

donde something tiene tipo T,

Puede, teóricamente, llamar a cualquiera de dos

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

La primera definición es posible porque OutputAndConsole heredó el operator<< función miembro operator<< de std::ofstream . La segunda forma es proporcionada por usted.

Cuando something es una variable, usa la segunda definición.

Cuando something es una plantilla, no puede usar la segunda definición, ya que no hay manera de determinar los parámetros de la plantilla. Por eso usa la primera definición. Por lo tanto,

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

es equivalente a

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

Lo podemos ver por el siguiente código:

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

Este código NO imprime "X".


Para que funcione, crearía mi propio conjunto de manipuladores:

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


Sugiero no implementar la funcionalidad de flujo de E / S estándar a través de la interfaz de flujo, sino la interfaz de flujo de datos. Una interfaz de flujo personalizada conduce a problemas tan pronto como el flujo se reconoce como std :: istream / std :: ostream, solo (debido a que algún operador, manipulador o función toma una referencia a un flujo).

Puede utilizar:

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

Tenga en cuenta que la única función que aplica cambios a la interfaz std :: basic_streambuf es virtual int sync() override omisión de virtual int sync() override .

Las clases de transmisión básicas estándar no proporcionan ninguna interfaz además de derivar e inicializar una clase de transmisión personalizada. La interfaz real (virtual) es el buffer de flujo estándar basic_streambuf .


std::endl es una función, no una cadena. Su método sobrecargado toma una cadena para la sobrecarga, por lo que no es este el que recibe llamadas cuando lo hace << std::endl

Necesita crear un operador que tome una función que tenga la misma firma que std:endl para realizar su sobrecarga.

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


struct OutputAndConsole : std::ofstream { // ... }; template <typename T> OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);

Como han mencionado otros, std::endl es una función de plantilla. Una función de plantilla no es un valor , es solo un nombre.

Una función de plantilla se puede convertir a un valor si intenta pasarla a una función que espera la función de una firma compatible. No se convierte a un valor si se pasa a una función de plantilla que toma T o const T& , porque el nombre de una función de plantilla representa una gran cantidad de valores posibles.

Debido a que std::endl no es un argumento válido para su operator<< , se ve en otra parte. Encuentra el operator<< std::ofstream operator<< que toma una función de manipulador de io mediante un puntero de función explícito.

Eso funciona, puede convertir endl a ese tipo de puntero de función! Así que, alegremente, lo llama.

Para solucionar este problema, agregue una sobrecarga del operator<< a OutputAndConsole que tome los punteros de la función del manipulador io.

La forma más fácil de hacerlo es escribir una función auxiliar:

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

luego dos << sobrecargas:

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

que hace que la plantilla std::endl encuentre una << coincidencia.