¿Cómo inicializar un std::stringstream?

1 minuto de lectura

Avatar de usuario de Dragons_Lair5
Dragons_Lair5

Necesito concatenar una cadena con enteros. Para hacer eso estoy usando stringstream de la siguiente manera:

int numPeople = 10;
stringstream ss;
ss << "Number of people is " << numPeople;

Y eso funcionó. Pero estaba tratando de hacerlo de la siguiente manera:

int numPeople = 10;
stringstream ss << "Number of people is " << numPeople;

Y estaba recibiendo el siguiente error: “inicializador esperado antes del token ‘“

¿Por qué estaba recibiendo este error? ¿Por qué no puedo asignar el stringstream valor al mismo tiempo que lo declaro?

  • Esa es la forma como es. Lo único que puede hacer en una declaración es construir el objeto que está declarando.

    – Brian Bi

    21 de febrero de 2014 a las 2:39

  • Supongo que esta alternativa de una sola línea sigue siendo una opción limpia. stringstream ss; ss << "Number of people is " << numPeople;

    – acegs

    18 de enero de 2018 a las 3:39


  • @Brian: incluso si el OP usó la palabra “declarar” en su pregunta, claramente no tenía la intención de limitarse a una declaración. Su única línea puede contener más de una llamada al constructor (por ejemplo, stringstream ss = "Number of people is "). Su problema era usar el valor de numPeople en ese forro.

    – sancho.s ReincorporarMonicaCellio

    18 de mayo de 2019 a las 13:08

Avatar de usuario de Tony Delroy
tony delroy

stringstream ss << "Number of people is " << numPeople;

¿Por qué no puedo asignar el stringstream valor al mismo tiempo que lo declaro?

Esto es similar a esperar que esto funcione…

int x + 3 + 9;

… pero esto no se analiza como una definición de variable, y mucho menos como una definición y asignación.

La forma legal de definir e inicializar un objeto

Las únicas formas de definir y inicializar una variable con un valor no predeterminado son (en un sentido gramatical, esto no es código):

type identifier(...args...);
type identifier{...args...};
type identifier = ...expression...;

La última notación es equivalente a la primera, es decir type identifier(arg) donde se pasa arg ...expression....

Tratando de usar la notación legal para int y stringstream

Para intpuede corregir fácilmente el código:

int x = 3 + 9;

…y funciona porque “3 + 9” se puede evaluar de forma independiente primero para dar un valor sensato para almacenar en x. El comportamiento del compilador para el operador. + en ints hace lo que queremos: produce el int resultado que luego queremos almacenar en x. Puedes pensar en lo anterior como:

        // evaluate the expression to assign first...
        ((int)3 + (int)9); // notate the implicit types
        (int)12;           // evaluate expression => value to assign
int x = (int)12;           // then think about doing the assignment
int x((int)12);            // construct a valid value

¡Funciona! Pero si lo intentas por stringstream

stringstream ss = "Number of people is " << numPeople;  // BROKEN
                  "Number of people is " << numPeople; // bitshift?!

…no funcionará, porque "Number of people is " << numPeople primero debe evaluarse, pero es ilegal: obtendrá un error como:

error C2296: '<<' : illegal, left operand has type 'const char [20]'

El problema es que el compilador todavía está tratando de aplicar la operación de desplazamiento bit a bit, que solo tiene sentido para los números, porque las sobrecargas para << que queremos usar requieren que cualquier código “X ostream&. Un literal de cadena no se puede convertir. En este punto, el compilador es ajeno a la stringstream al que se le pasará el resultado de la expresión.

Una solución para stringstream

Es un poco como un problema de huevo y gallina, porque necesita combinar los valores de la mano derecha que desea en el stringstream llamar a la stringstream‘s constructor, pero para eso necesitas… un stringstream. De hecho, puedes lograr eso con un temporal stringstream:

static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople)

Desafortunadamente se necesita el elenco porque el operator<< mango de sobrecarga stringstreams a través de referencias a sus ostream clase base, devolviendo un ostream&por lo que debe volver a lanzar a la stringstream escriba manualmente, por lo que luego puede invocar el std::stringstream mover constructor…

La construcción completa de una sola línea es entonces…

std::ostringstream ss(static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople));
...or...
auto&& ss = static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople);

… pero eso es demasiado horrible para contemplarlo.

Hacer que la solución (posiblemente) sea menos horrible con macros

Sí, lo leiste bien. Dependiendo de su sensibilidad, puede sentir que una macro ayuda o es peor…

#define OSS(VALUES) \
    static_cast<std::ostringstream&&>(std::ostringstream{} << VALUES)

auto&& ss = OSS("Number of people is " << numPeople);

FWIW, también podrías usar la macro para crear cadenas…

auto&& s = OSS("Number of people is " << numPeople).str(); 

…o crear una macro dedicada…

#define STR(VALUES) \
    static_cast<std::ostringstream&&>(std::ostringstream{} << VALUES).str()
auto&& s = STR("Number of people is " << numPeople);

Una (posiblemente) mejor práctica: construcción e inicialización separadas

Solo crea el stringstream – opcionalmente proporcionando un solo string al constructor – luego use operator<< en una segunda declaración:

std::stringstream ss;
ss << "Number of people is " << numPeople;

Esto es mucho más fácil de leer y no se requieren macros extrañas.

Una alternativa

Introducción de C++11 to_string() sobrecargas que son convenientes si tiene un valor integral o dos para concatenar con o en un string:

auto&& s = "Number of people is " + std::to_string(numPeople);

Sin embargo, esto puede ser ineficiente (verifique las capacidades de optimización de su(s) compilador(es) si le importa): cada std::to_string() es probable que asigne dinámicamente un búfer para un independiente std::string Por ejemplo, las concatenaciones individuales pueden implicar una copia adicional de texto, y los búfer originales asignados dinámicamente pueden necesitar ser ampliados, entonces la mayoría de esos temporales std::stringLos correos electrónicos tardarán en desasignarse durante la destrucción.

Discusión

Idealmente, std::stringstream tendría un constructor aceptando un número arbitrario de argumentos de constructor (A, B, C...) para ser formateado en el stringstream como si fuera una posterior << A << B << C.... Ya hay constructores con argumentos (p. ej. (std::ios_base::openmode, const Allocator&)), por lo que necesitaríamos un marcador de posición para distinguir dichos argumentos de los valores que estamos tratando de formatear en la secuencia, o una solución más extraña como requerir que los valores que se formatearán en la secuencia se pasen como una lista inicializadora.

Aún así, se ve y se siente muy raro usando cuerdas con , en lugar de <<:

std::stringstream ss{"n:", std::setw(4), std::hex, '\n');

Y luego, si durante el mantenimiento del código encuentra que necesita mover los valores de transmisión a un punto después de la construcción, deberá cambiar el separador. Dividirlo en dos líneas para empezar, construcción y luego transmisión, simplifica ese mantenimiento.

Era peor en C++03

C++03 carecía de constructores de movimiento, por lo que era necesario usar el std::ostringstream::str() función de miembro en el temporal para obtener una copia extra profunda del std::string con el que construir el nombrado stringsteam

stringstream ss(static_cast<std::ostringstream&>(std::ostringstream() << "Number of people is " << numPeople).str());

Con este código C++03, existe la posibilidad de que se dupliquen las asignaciones de memoria dinámica (a menos que las cadenas sean lo suficientemente cortas para caber dentro del objeto de cadena, un std::string técnica llamada “Optimización de cadenas cortas” o SSO). También hay una copia profunda del contenido textual. La construcción seguida de transmisión fue un mejor enfoque.

El stringbuf debe inicializarse antes de que se le puedan agregar valores. stringstream es un objeto.

Haciendo:

std::stringstream ss;

Básicamente, está permitiendo que la aplicación asigne espacio para manejar los valores que se agregarán.

¿Ha sido útil esta solución?