C ++ moderno hace que esto sea súper simple.
C ++ 20
C ++ 20 introduce std::format
, lo que le permite hacer exactamente eso. Utiliza campos de reemplazo similares a los de Python :
#include <iostream>
#include <format>
int main() {
std::cout << std::format("Hello {}!\n", "world");
}
¡Mira la documentación completa ! Es una gran mejora en la calidad de vida.
C ++ 11
Con C ++ 11 s std::snprintf
, esto ya se convirtió en una tarea bastante fácil y segura.
#include <memory>
#include <string>
#include <stdexcept>
template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
std::unique_ptr<char[]> buf( new char[ size ] );
snprintf( buf.get(), size, format.c_str(), args ... );
return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}
El fragmento de código anterior está licenciado bajo CC0 1.0 .
Explicación línea por línea:
Objetivo: escribir en achar*
usando std::snprintf
y luego convertir eso en astd::string
.
Primero, determinamos la longitud deseada de la matriz de caracteres usando una condición especial en snprintf
. Desde cppreference.com :
Valor de retorno
[...] Si la cadena resultante se trunca debido al límite de tamaño buf_size, la función devuelve el número total de caracteres (sin incluir el byte nulo de terminación) que se habrían escrito si no se impusiera el límite.
Esto significa que el tamaño deseado es el número de caracteres más uno , de modo que el terminador nulo se ubicará después de todos los demás caracteres y que el constructor de cadenas puede cortarlo nuevamente. Este problema fue explicado por @ alexk7 en los comentarios.
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;
snprintf
devolverá un número negativo si se produjo un error, por lo que luego verificamos si el formato funcionó como se desea. No hacer esto podría conducir a errores silenciosos o la asignación de un gran búfer, como lo señala @ead en los comentarios.
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
A continuación, asignamos una nueva matriz de caracteres y la asignamos a a std::unique_ptr
. Esto generalmente se recomienda, ya que no tendrá delete
que volver a hacerlo manualmente .
Tenga en cuenta que esta no es una forma segura de asignar un unique_ptr
tipo definido por el usuario, ya que no puede desasignar la memoria si el constructor arroja una excepción.
std::unique_ptr<char[]> buf( new char[ size ] );
Después de eso, por supuesto, podemos usarlo snprintf
para su uso previsto y escribir la cadena formateada en el char[]
.
snprintf( buf.get(), size, format.c_str(), args ... );
Finalmente, creamos y devolvemos uno nuevo a std::string
partir de eso, asegurándonos de omitir el terminador nulo al final.
return std::string( buf.get(), buf.get() + size - 1 );
Puedes ver un ejemplo en acción aquí .
Si también desea usar std::string
en la lista de argumentos, eche un vistazo a esta esencia .
Información adicional para usuarios de Visual Studio :
Como se explica en esta respuesta , Microsoft renombró std::snprintf
a _snprintf
(sí, sin std::
). MS lo configuró como obsoleto y recomienda usarlo _snprintf_s
en su lugar, sin embargo _snprintf_s
, no aceptará que el búfer sea cero o menor que la salida formateada y no calculará la longitud de las salidas si eso ocurre. Entonces, para deshacerse de las advertencias de desaprobación durante la compilación, puede insertar la siguiente línea en la parte superior del archivo que contiene el uso de _snprintf
:
#pragma warning(disable : 4996)
Pensamientos finales
Muchas respuestas a esta pregunta se escribieron antes de la época de C ++ 11 y usan longitudes de búfer fijas o vargs. A menos que esté atascado con versiones antiguas de C ++, no recomendaría usar esas soluciones. Idealmente, siga el camino C ++ 20.
Debido a que la solución C ++ 11 en esta respuesta usa plantillas, puede generar bastante código si se usa mucho. Sin embargo, a menos que esté desarrollando para un entorno con un espacio muy limitado para binarios, esto no será un problema y sigue siendo una gran mejora con respecto a las otras soluciones, tanto en claridad como en seguridad.
Si la eficiencia del espacio es muy importante, estas dos soluciones con vargs y vsnprintf pueden ser útiles.
NO UTILICE ninguna solución con longitudes de búfer fijas, eso es solo pedir problemas.
boost::format
(como la solución de kennytm usa aquí ).boost::format
¡Ya es compatible con los operadores de transmisión C ++ también! ejemplo:cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;
.boost::format
tiene la menor cantidad de líneas de código ... es revisado por pares y se integra muy bien con las secuencias de C ++.