2008-11-19 14 views
14

Estoy tratando de escribir una macro que me permita hacer algo como: FORMAT(a << "b" << c << d), y el resultado sería una cadena - lo mismo que crear un ostringstream, insertar a...d, y devolver .str() . Algo como:C++ formato macro/inline ostringstream

string f(){ 
    ostringstream o; 
    o << a << "b" << c << d; 
    return o.str() 
} 

Esencialmente, FORMAT(a << "b" << c << d) == f().

En primer lugar, he intentado:

1: #define FORMAT(items)             \ 
    ((std::ostringstream&)(std::ostringstream() << items)).str() 

Si el primer elemento es una cadena C (const char *), se imprimirá la dirección de la cadena en hexadecimal, y los siguientes artículos se imprimirá bien. Si el primer elemento es std::string, no se compilará (no se aplica el operador <<).

Este:

2: #define FORMAT(items)             \ 
    ((std::ostringstream&)(std::ostringstream() << 0 << '\b' << items)).str() 

da lo que parece ser la salida derecha, pero el 0 y \b están presentes en la cadena, por supuesto.

El siguiente parece funcionar, pero compila con las advertencias (tomando la dirección del temporal):

3: #define FORMAT(items)             \ 
    ((std::ostringstream&)(*((std::ostream*)(&std::ostringstream())) << items)).str() 

¿alguien sabe por qué 1 imprime la dirección de la c-secuencia y falla al compilar con el std::string? ¿No son 1 y 3 esencialmente lo mismo?

Sospecho que las plantillas variadic de C++ 0x harán posible format(a, "b", c, d). Pero, ¿hay alguna manera de resolver esto ahora?

Respuesta

19

Usted ha clavado todos más o menos esto ya. Pero es un poco difícil de seguir. Por eso quiero aprovechar una puñalada en un resumen de lo que ha dicho ...


que las dificultades que aquí son:

  • Estamos jugando con un ostringstream objeto temporal, por lo que tomar direcciones está contraindicado -indicado.

  • Como es temporal, no podemos convertir trivialmente a un objeto ostream a través del casting.

  • Tanto el constructor [obviamente] como str() son métodos de clase ostringstream. (Sí, tenemos que utilizar .str(). Utilizando el objeto ostringstream directamente terminaría invocando ios::operator void*(), el retorno de una buena mala valor del puntero similar a/y no un objeto de cadena.)

  • operator<<(...) existe como heredaron ostream métodos y funciones globales. En todos los casos, devuelve una referencia ostream&.

  • Las opciones aquí para ostringstream()<<"foo" son el método heredado ostream::operator<<(void*) y la función global operator<<(ostream&,const char*). El ostream::operator<<(void*) heredado gana porque no podemos convertir a una referencia de objeto ostream para invocar la función global. [Felicitaciones a coppro!]


Por lo tanto, para sacar esto adelante, tenemos que:

  • asignar una ostringstream temporal.
  • Convertirlo a ostream.
  • Añadir datos.
  • Convertirlo de nuevo a ostringstream.
  • E invoque str().

Asignación:ostringstream().

Conversión: Hay varias opciones. Otros han sugerido:

  • ostringstream() << std::string() // Kudos to *David Norman*
  • ostringstream() << std::dec // Kudos to *cadabra*

O podríamos utilizar:

no podemos utilizar:

  • operator<<(ostringstream(), "")
  • (ostream &) ostringstream()

Adjuntar: Directo ahora.

conversión de vuelta: Sólo podría utilizar (ostringstream&). Pero un dynamic_cast sería más seguro. En el caso improbable de que dynamic_cast haya devuelto NULL (no debería), el siguiente .str() activará una bomba nuclear.

Invocando str(): Guess.


Poniendo todo junto.

#define FORMAT(ITEMS)            \ 
    ((dynamic_cast<ostringstream &> (       \ 
     ostringstream() . seekp(0, ios_base::cur) << ITEMS) \ 
    ) . str()) 

Referencias:

.

4

El problema que tiene está relacionado con el hecho de que operator << (ostream&, char*) no es miembro de ostream, y su instancia de ostream temporal no se puede enlazar a una referencia que no sea const. En su lugar, elige la sobrecarga void*, que es miembro de ostream, y por lo tanto no tiene esa restricción.

Lo mejor (pero no el más fácil ni el más elegante, por mucho esfuerzo) sería usar el preprocesador Boost para generar un gran número de sobrecargas de funciones, cada plantilla en una gran cantidad de objetos (se han omitido y asumiendo using namespace std;):

#define MAKE_OUTPUT(z, n, data) \ 
    BOOST_PP_TUPLE_ELEM(2, 0, data) << BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(2, 1, data), n); 

#define MAKE_FORMAT(z, n, data) \ 
    template <BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), typename T)> \ 
    inline string format(BOOST_PP_ENUM_BINARY_PARAMS_Z(z, BOOST_PP_INC(n), T, p)) \ 
    { \ 
     ostringstream s; \ 
     BOOST_PP_REPEAT_##z(z, n, MAKE_OUTPUT, (s, p)); \ 
     return s.str(); \ 
    } 

no está garantizado para trabajar exactamente (lo escribió sin pruebas), pero eso es básicamente la idea. A continuación, llama al BOOST_PP_REPEAT(N, MAKE_FORMAT,()) para crear una serie de funciones que toman hasta N parámetros que formatearán tu cadena como desees (reemplaza N con el número entero de preferencia. Los valores más altos pueden afectar negativamente los tiempos de compilación). Esto debería ser suficiente hasta que obtenga un compilador con plantillas variadas. Debería leer la documentación del preprocesador boost, tiene funciones muy potentes para cosas como esta. (Se puede posteriormente #undef las macros, después de llamar a la invocación BOOST_PP_REPEAT para generar las funciones)

+0

Rechazaré el primer párrafo, pero no toda la respuesta. –

+0

Gracias, eso es muy informativo. No he usado Boost mucho, es interesante ver lo que hay allí. – cadabra

+0

¿Por qué no se puede unir? –

1

Aquí es una solución de trabajo:

#define FORMAT(items)             \ 
    ((std::ostringstream&)(std::ostringstream() << std::dec << items)).str() 

Yo no entiendo muy bien el comportamiento del primer argumento.

+0

std :: dec indica a la transmisión que muestre los números como base 10 o decimal. –

+0

Utiliza uno de los formateadores miembros para luego devolver una referencia, que puede vincularse a un temporal. std :: dec es un buen parámetro para aprobar que no tendrá un efecto secundario en la salida de la transmisión. – coppro

0

¿Por qué no usar simplemente una función en lugar de una macro?

+0

porque quiere usar la sintaxis del operador de inserción –

+0

Supongo que podría escribir manualmente N funciones de plantilla, una para cada número de argumentos y luego usar el formato (a, "b", c, d). O use la solución de coppro para generarlos. Pero ninguno es bonito. – cadabra

2

Aquí es una respuesta como la de cadabra que no pierde el tiempo con el estado ostream:

#define FORMAT(items)  static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str() 

creo que el primer párrafo de la respuesta de coppro describe por qué las cosas se comportan de esta manera.

+0

No se compila en gcc 4.0.1. Aquí hay uno que no juega con el estado, pero sí copia una cadena. #define FORMAT (elementos) \ ((std :: ostringstream &) (std :: ostringstream() << 0 << elementos)). Str(). Substr (1) – cadabra

+0

inteligente tweak, cadabra –

+0

Solo intenté Microsoft VisualStudio 2008 –

20

Esto es lo que uso. Todo encaja en una definición de clase ordenada en un archivo de encabezado.

actualización: mejora importante al código gracias a litb.

// makestring.h: 

class MakeString 
{ 
    public: 
     std::stringstream stream; 
     operator std::string() const { return stream.str(); } 

     template<class T> 
     MakeString& operator<<(T const& VAR) { stream << VAR; return *this; } 
};

Aquí es cómo se utiliza:

string myString = MakeString() << a << "b" << c << d;
+0

Su segundo ejemplo le dará un puntero en una cadena temporal que ha sido destruida, buena suerte con eso. –

+0

Probé esa porción, y no compila de todos modos. ¡Lo he eliminado de mi respuesta! Gracias por la aportación. –

+0

sin necesidad de una macro: poner la plantilla StringMaker & operator << (T const & VAR) {Stream << VAR; devuelve * esto; } dentro de su definición de clase y elimine "ref" –

1

Cuando tomé la solución de mrree (la marcada como "preferida", la bellamente explicada y la que funcionaba perfectamente para G ++), tuve problemas con MSVC++: todas las cadenas compiladas con esta macro terminaron vacías.

Horas (y muchos rasguños en mi cabeza y haciendo una pregunta "reloaded" aquí) más tarde, descubrí que la llamada a seekp() era la culpable. No estoy seguro de lo que hace MSVC++ diferente con eso, pero sustituyendo

ostringstream().seekp(0, ios_base::cur) 

con

ostringstream() << std::dec 

obras de cadabra para MSVC++, también.