Manipulador de flujo personalizado C ++ que cambia el siguiente elemento en la secuencia

En C ++, para imprimir un número en hexadecimal, haga esto:

int num = 10; std::cout << std::hex < 'a' 

Sé que puedo crear un manipulador que simplemente agrega cosas a la transmisión de la siguiente manera:

 std::ostream& windows_feed(std::ostream& out) { out << "\r\n"; return out; } std::cout << "Hello" < "Hello\r\n" 

Sin embargo, ¿cómo puedo crear un manipulador que, como ‘hex’, modifique los elementos para que entren en la transmisión? Como un ejemplo simple, ¿cómo crearía el manipulador de másona aquí ?:

 int num2 = 1; std::cout << "1 + 1 = " << plusone < "1 + 1 = 2" // note that the value stored in num2 does not change, just its display above. std::cout < "1" 

Primero, debe almacenar un estado en cada flujo. Puedes hacer eso con la función iword y un índice que le pases, dado por xalloc :

 inline int geti() { static int i = ios_base::xalloc(); return i; } ostream& add_one(ostream& os) { os.iword(geti()) = 1; return os; } ostream& add_none(ostream& os) { os.iword(geti()) = 0; return os; } 

Teniendo eso en su lugar, ya puedes recuperar algún estado en todas las transmisiones. Ahora, solo tiene que enganchar en la operación de salida respectiva. El resultado numérico lo realiza una faceta, ya que potencialmente depende de la configuración regional. Entonces puedes hacer

 struct my_num_put : num_put { iter_type do_put(iter_type s, ios_base& f, char_type fill, long v) const { return num_put::do_put(s, f, fill, v + f.iword(geti())); } iter_type do_put(iter_type s, ios_base& f, char_type fill, unsigned long v) const { return num_put::do_put(s, f, fill, v + f.iword(geti())); } }; 

Ahora, puedes probar las cosas.

 int main() { // outputs: 11121011 cout.imbue(locale(locale(),new my_num_put)); cout << add_one << 10 << 11 << add_none << 10 << 11; } 

Si desea que solo se incremente el siguiente número, simplemente configure la palabra a 0 nuevamente después de cada llamada a do_put .

Estoy totalmente de acuerdo con Neil Butterworth en este caso, sin embargo, en el caso específico que está utilizando, podría hacer este truco totalmente horrible. No hagas esto en ningún código de producción. Tiene muchos errores. Por un lado, solo funciona en el one-liner anterior, no cambia el estado de la transmisión subyacente.

 class plusone_stream : public std::ostream { public: std::ostream operator<<(int i) { _out << i+1; return *this; } }; std::ostream& plusone(std::ostream& out) { return plusone_stream(out); } 

No es una respuesta directa a su pregunta, pero ¿no cree que el uso de una función simple es más simple de implementar y más fácil de usar que escribir un manipulador completo?

 #include  template std::string plusone(T const& t) { std::ostringstream oss; oss << (t + 1); return oss.str(); } 

Uso:

 cout << plusone(42); 

Al decir "claro para usar", quiero decir que el usuario no tiene que preguntarse a sí mismo, "¿afecta solo el siguiente elemento o todos los elementos subsiguientes?" Es obvio por la inspección que solo se ve afectado el argumento de la función.

(Para el ejemplo de plusone() , puede simplificar aún más simplemente devolviendo una T lugar, pero devolver una std::string sirve para el caso general).

una solución simple para tu caso de prueba sin usar . No puedo prometer que el mismo enfoque funcionará en la vida real.

El enfoque básico es que cout << plusone devuelve un objeto auxiliar temporal ( PlusOnePlus ), que a su vez tiene el operator << sobrecargado operator << que realiza la adición.

Lo he probado en Windows:

 PlusOne plusone; cout << plusone << 41 

produce "42", como se esperaba. Aquí está el código:

 class PlusOnePlus { public: PlusOnePlus(ostream& os) : m_os(os) {} // NOTE: This implementation relies on the default copy ctor, // assignment, etc. private: friend ostream& operator << (PlusOnePlus& p, int n); ostream& m_os; }; class PlusOne { public: static void test(ostream& os); }; PlusOnePlus operator << (ostream& os, const PlusOne p) { return PlusOnePlus(os); } ostream& operator << (PlusOnePlus& p, int n) { return p.m_os << n + 1; } void PlusOne::test(ostream& os) { PlusOne plusone; os << plusone << 0 << endl; os << plusone << 41 << endl; } 

EDITAR: Comentó el código para señalar que estoy confiando en el constructor de copia predeterminado (etc.) para PlusOnePlus . Una implementación robusta probablemente definiría estos

Tendrás que jugar con streamstates. He marcado los siguientes enlaces en el tema:

  • Una discusión sobre impulsar ML
  • El artículo de Maciej Sobczak sobre CUJ / DDJ

Como la biblioteca de Maciej Sobczak ya no está disponible en línea, y como la licencia me permite hacerlo, (corríjame si me equivoco), aquí hay una copia de su archivo principal que he logrado salvar del olvido:

 // streamstate.h // // Copyright (C) Maciej Sobczak, 2002, 2003 // // Permission to copy, use, modify, sell and distribute this software is // granted provided this copyright notice appears in all copies. This software // is provided "as is" without express or implied warranty, and with no claim // as to its suitability for any purpose. // //  //  //  #ifndef STREAMSTATE_H_INCLUDED #define STREAMSTATE_H_INCLUDED #include  #include  #include  // helper exception class, thrown when the source of error // was in one of the functions managing the additional state storage class StreamStateException : public std::ios_base::failure { public: explicit StreamStateException() : std::ios_base::failure( "Error while managing additional IOStream state.") { } }; // State should be: // default-constructible // copy-constructible // assignable // note: the "void *" slot is used for storing the actual value // the "long" slot is used to propagate the error flag template < class State, class charT = char, class traits = std::char_traits > class streamstate { public: // construct with the default state value streamstate() {} // construct with the given stream value streamstate(const State &s) : state_(s) {} // modifies the stream std::basic_ios & modify(std::basic_ios &ios) const { long *errslot; void *&p = state_slot(ios, errslot); // propagate the error flag to the real stream state if (*errslot == std::ios_base::badbit) { ios.setstate(std::ios_base::badbit); *errslot = 0; } // here, do-nothing-in-case-of-error semantics if (ios.bad()) return ios; if (p == NULL) { // copy existing state object if this is new slot p = new State(state_); ios.register_callback(state_callback, 0); } else *static_cast(p) = state_; return ios; } // gets the current (possibly default) state from the slot static State & get(std::basic_ios &ios) { long *errslot; void *&p = state_slot(ios, errslot); // propagate the error flag to the real stream state if (*errslot == std::ios_base::badbit) { ios.setstate(std::ios_base::badbit); *errslot = 0; } // this function returns a reference and therefore // the only sensible error reporting is via exception if (ios.bad()) throw StreamStateException(); if (p == NULL) { // create default state if this is new slot p = new State; ios.register_callback(state_callback, 0); } return *static_cast(p); } private: // manages the destruction and format copying // (in the latter case performs deep copy of the state) static void state_callback(std::ios_base::event e, std::ios_base &ios, int) { long *errslot; if (e == std::ios_base::erase_event) { // safe delete if state_slot fails delete static_cast(state_slot(ios, errslot)); } else if (e == std::ios_base::copyfmt_event) { void *& p = state_slot(ios, errslot); State *old = static_cast(p); // Standard forbids any exceptions from callbacks try { // in-place deep copy p = new State(*old); } catch (...) { // clean the value slot and // set the error flag in the error slot p = NULL; *errslot = std::ios_base::badbit; } } } // returns the references to associated slot static void *& state_slot(std::ios_base &ios, long *&errslot) { static int index = std::ios_base::xalloc(); void *&p = ios.pword(index); errslot = &(ios.iword(index)); // note: if pword failed, // then p is a valid void *& initialized to 0 // (27.4.2.5/5) return p; } State state_; }; // partial specialization for iword functionality template < class charT, class traits > class streamstate { public: // construct with the default state value streamstate() {} // construct with the given stream value streamstate(long s) : state_(s) {} // modifies the stream // the return value is not really useful, // it has to be downcasted to the expected stream type std::basic_ios & modify(std::basic_ios &ios) const { long &s = state_slot(ios); s = state_; return ios; } static long & get(std::basic_ios &ios) { return state_slot(ios); } private: static long & state_slot(std::basic_ios &ios) { static int index = std::ios_base::xalloc(); long &s = ios.iword(index); // this function returns a reference and we decide // to report errors via exceptions if (ios.bad()) throw StreamStateException(); return s; } long state_; }; // convenience inserter for ostream classes template < class State, class charT, class traits > std::basic_ostream & operator<<(std::basic_ostream &os, const streamstate &s) { s.modify(os); return os; } // convenience extractor for istream classes template < class State, class charT, class traits > std::basic_istream & operator>>(std::basic_istream &is, const streamstate &s) { s.modify(is); return is; } // the alternative if there is a need to have // many different state values of the same type // here, the instance of streamstate_value encapsulates // the access information (the slot index) template < class State, class charT = char, class traits = std::char_traits > class streamstate_value { public: streamstate_value() : index_(-1) { } // returns a reference to current (possibly default) state State & get(std::basic_ios &ios) { long *errslot; void *&p = state_slot(ios, errslot, index_); // propagate the error flag to the real stream state if (*errslot == std::ios_base::badbit) { ios.setstate(std::ios_base::badbit); *errslot = 0; } // this function returns a reference and the only // sensible way of error reporting is via exception if (ios.bad()) throw StreamStateException(); if (p == NULL) { // create default state if this is new slot p = new State; ios.register_callback(state_callback, index_); } return *static_cast(p); } private: // manages the destruction and format copying // (in the latter case performs deep copy of the state) static void state_callback(std::ios_base::event e, std::ios_base &ios, int index) { long *errslot; if (e == std::ios_base::erase_event) { // safe delete if state_slot fails delete static_cast(state_slot(ios, errslot, index)); } else if (e == std::ios_base::copyfmt_event) { void *& p = state_slot(ios, errslot, index); State *old = static_cast(p); // Standard forbids any exceptions from callbacks try { // in-place deep copy p = new State(*old); } catch (...) { // clean the value slot and set the error flag // in the error slot p = NULL; *errslot = std::ios_base::badbit; } } } // returns the references to associated slot static void *& state_slot(std::ios_base &ios, long *& errslot, int & index) { if (index < 0) { // first index usage index = std::ios_base::xalloc(); } void *&p = ios.pword(index); errslot = &(ios.iword(index)); // note: if pword failed, // then p is a valid void *& initialized to 0 // (27.4.2.5/5) return p; } int index_; }; // partial specialization for iword functionality template < class charT, class traits > class streamstate_value { public: // construct with the default state value streamstate_value() : index_(-1) { } long & get(std::basic_ios &ios) { if (index_ < 0) { // first index usage index_ = std::ios_base::xalloc(); } long &s = ios.iword(index_); if (ios.bad()) throw StreamStateException(); return s; } private: long index_; }; #endif // STREAMSTATE_H_INCLUDED 

El enfoque de litb es “el camino correcto” y necesario para cosas complicadas, pero algo como esto puede ser lo suficientemente bueno. Agregue privacidad y amistad al gusto.

 struct PlusOne { PlusOne(int i) : i_(i) { } int i_; }; std::ostream & operator<<(std::ostream &o, const PlusOne &po) { return o << (po.i_ + 1); } std::cout << "1 + 1 = " << PlusOne(num2); // => "1 + 1 = 2" 

En este ejemplo simple, crear y transmitir un objeto temporal no parece mucho más útil que definir una función más Uno () como alguien ya sugirió. Pero supongamos que quieres que funcione así:

 std::ostream & operator<<(std::ostream &o, const PlusOne &po) { return o << po.i_ << " + 1 = " << (po.i_ + 1); } std::cout << PlusOne(num2); // => "1 + 1 = 2" 

Los manipuladores hex , dec y oct simplemente cambian la propiedad de campo base de la stream existente.

Consulte la referencia de C ++ para obtener más información acerca de estos manipuladores.

Tal como se publicó en la respuesta de Neil Butterworth , necesitaría ampliar las clases de flujo existentes, o crear las suyas propias, para tener manipuladores que afecten valores futuros insertados en la secuencia.

En el ejemplo de su manipulador de plusone , el objeto de secuencia debería tener un indicador interno para indicar que se debe agregar uno a todos los valores insertados. El manipulador de plusone simplemente establecería ese indicador, y el código para manejar la inserción del flujo verificaría ese indicador antes de insertar números.