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?
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 int
puede 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 int
s 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 stringstream
s 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::string
Los 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.
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 denumPeople
en ese forro.– sancho.s ReincorporarMonicaCellio
18 de mayo de 2019 a las 13:08