std :: formatage de chaîne comme sprintf

454

Je dois formater std::stringavec sprintfet l'envoyer en flux de fichiers. Comment puis-je faire ceci?

Max Frai
la source
6
longue histoire courte utilisation boost::format(comme la solution de kennytm l'utilise ici ). boost::formatprend également en charge les opérateurs de flux C ++! exemple: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formata le moins de lignes de code ... est évalué par les pairs et s'intègre bien avec les flux C ++.
Trevor Boyd Smith
@Ockonal - Pour le bien de la communauté (je me fiche de mon représentant), je vous suggère de modifier votre sélection. Celui actuellement sélectionné, dans le premier extrait, présente un bogue en attente de se produire dans son utilisation d'une longueur maximale arbitraire. Le deuxième extrait ignore complètement votre désir déclaré d'utiliser des vargs comme sprintf. Je vous suggère de sélectionner la SEULE réponse ici qui est propre, sûre, ne repose que sur les normes C ++, testée et bien commentée. Que ce soit le mien n'est pas pertinent. C'est objectivement vrai. Voir stackoverflow.com/questions/2342162/… .
Douglas Daseeco
@TrevorBoydSmith a std::formatété ajouté à C ++ 20 BTW: stackoverflow.com/a/57286312/895245 Awesome!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli j'ai lu un article sur C++20hier et j'ai vu que C++20copié boost(pour la millionième fois maintenant) en ajoutant le std::formatà la C++20spécification! J'étais très très content! Presque tous les fichiers C ++ que j'ai écrits au cours des 9 dernières années ont été utilisés boost::format. ajouter une sortie de style printf officielle aux flux en C ++ ira un long chemin IMO pour tout C ++.
Trevor Boyd Smith

Réponses:

333

Vous ne pouvez pas le faire directement, car vous n'avez pas accès en écriture au tampon sous-jacent (jusqu'à C ++ 11; voir le commentaire de Dietrich Epp ). Vous devrez le faire d'abord dans une chaîne c, puis le copier dans une chaîne std :::

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Mais je ne sais pas pourquoi vous n'utiliseriez pas simplement un flux de chaînes? Je suppose que vous avez des raisons spécifiques de ne pas simplement faire ceci:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
Doug T.
la source
17
Le cookie magique en char buf[100];fait cette solution peu robuste. Mais l'idée essentielle est là.
John Dibling
18
John, les flux ne sont pas lents. La seule raison pour laquelle les flux semblent lents est que, par défaut, les iostreams se synchronisent avec la sortie C FILE afin que cout et printfs mélangés soient sortis correctement. La désactivation de ce lien (avec un appel à cout.sync_with_stdio (false)) fait que les flux de c ++ surpassent stdio, au moins depuis MSVC10.
Jimbo
72
La raison d'utiliser des formats est de laisser un localisateur reconstruire la structure de la phrase pour les langues étrangères, au lieu de coder en dur la grammaire de la phrase.
Martijn Courteaux
216
Pour une raison quelconque, d'autres langages utilisent une syntaxe semblable à printf: Java, Python (la nouvelle syntaxe est encore plus proche de printf que des flux). Seul C ++ inflige cette abomination verbeuse à des êtres humains innocents.
quant_dev
9
Encore mieux, utilisez asprintf, qui alloue une nouvelle chaîne avec suffisamment d'espace pour contenir le résultat. Copiez ensuite cela sur un std::stringsi vous le souhaitez, et rappelez-vous de freel'original. En outre, il est possible de mettre cela dans une macro afin que tout bon compilateur aide à valider le format pour vous - vous ne voulez pas mettre un doubleoù un %sest attendu
Aaron McDaid
286

Le C ++ moderne rend cela super simple.

C ++ 20

C ++ 20 introduit std::format, ce qui vous permet de faire exactement cela. Il utilise des champs de remplacement similaires à ceux de python :

#include <iostream>
#include <format>

int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

Consultez la documentation complète ! C'est une énorme amélioration de la qualité de vie.


C ++ 11

Avec C ++ 11 s std::snprintf, cela est déjà devenu une tâche assez facile et sûre.

#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
}

L'extrait de code ci-dessus est sous licence CC0 1.0 .

Explication ligne par ligne:

Objectif: écrire dans achar*en utilisant std::snprintfpuis convertir cela en astd::string.

Tout d'abord, nous déterminons la longueur souhaitée du tableau char en utilisant une condition spéciale dans snprintf. De cppreference.com :

Valeur de retour

[...] Si la chaîne résultante est tronquée à cause de buf_size limit, la fonction retourne le nombre total de caractères (sans compter l'octet nul final) qui auraient été écrits si la limite n'avait pas été imposée.

Cela signifie que la taille souhaitée est le nombre de caractères plus un , de sorte que le terminateur nul se place après tous les autres caractères et qu'il peut être à nouveau coupé par le constructeur de chaîne. Ce problème a été expliqué par @ alexk7 dans les commentaires.

size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintfretournera un nombre négatif en cas d'erreur, nous vérifions donc si le formatage a fonctionné comme souhaité. Ne pas le faire pourrait entraîner des erreurs silencieuses ou l'allocation d'un énorme tampon, comme indiqué par @ead dans les commentaires.

if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

Ensuite, nous allouons un nouveau tableau de caractères et l'affectons à a std::unique_ptr. Ceci est généralement conseillé, car vous n'aurez pas à deletele refaire manuellement .

Notez que ce n'est pas un moyen sûr d'allouer un unique_ptravec des types définis par l'utilisateur car vous ne pouvez pas désallouer la mémoire si le constructeur lève une exception!

std::unique_ptr<char[]> buf( new char[ size ] );

Après cela, nous pouvons bien sûr simplement utiliser snprintfcomme prévu et écrire la chaîne formatée dans le char[].

snprintf( buf.get(), size, format.c_str(), args ... );

Enfin, nous créons et retournons un nouveau à std::stringpartir de cela, en veillant à omettre le terminateur nul à la fin.

return std::string( buf.get(), buf.get() + size - 1 );

Vous pouvez voir un exemple en action ici .


Si vous souhaitez également utiliser std::stringdans la liste des arguments, jetez un œil à cet élément essentiel .


Informations supplémentaires pour les utilisateurs de Visual Studio :

Comme expliqué dans cette réponse , Microsoft renommé std::snprintfà _snprintf(oui, sans std::). MS le définit en outre comme obsolète et conseille de l'utiliser à la _snprintf_splace, mais _snprintf_sn'acceptera pas que le tampon soit nul ou inférieur à la sortie formatée et ne calculera pas la longueur des sorties si cela se produit. Ainsi, afin de vous débarrasser des avertissements de dépréciation lors de la compilation, vous pouvez insérer la ligne suivante en haut du fichier qui contient l'utilisation de _snprintf:

#pragma warning(disable : 4996)

Dernières pensées

Beaucoup de réponses à cette question ont été écrites avant l'époque de C ++ 11 et utilisent des longueurs de tampon fixes ou vargs. À moins que vous ne soyez bloqué avec les anciennes versions de C ++, je ne recommanderais pas d'utiliser ces solutions. Idéalement, suivez la voie C ++ 20.

Étant donné que la solution C ++ 11 dans cette réponse utilise des modèles, elle peut générer un peu de code si elle est beaucoup utilisée. Cependant, à moins que vous ne développiez pour un environnement avec un espace très limité pour les binaires, cela ne sera pas un problème et constitue toujours une amélioration considérable par rapport aux autres solutions, à la fois en termes de clarté et de sécurité.

Si l'efficacité de l'espace est super importante, ces deux solutions avec vargs et vsnprintf peuvent être utiles. N'UTILISEZ PAS de solutions avec des longueurs de tampon fixes, c'est juste pour des problèmes.

iFreilicht
la source
2
Veuillez souligner dans votre réponse pour les utilisateurs de Visual Studio que la version de VS doit être au moins 2013. De cet article, vous pouvez voir qu'il ne fonctionne qu'avec la version VS2013: si le tampon est un pointeur nul et que le nombre est nul, len est renvoyé comme le nombre de caractères requis pour formater la sortie, sans inclure le caractère nul final. Pour effectuer un appel réussi avec le même argument et les mêmes paramètres régionaux, allouez un tampon contenant au moins len + 1 caractères.
cha
3
@moooeeeep Plusieurs raisons. Tout d'abord, le but ici est de retourner une chaîne std ::, pas une chaîne c, donc vous vouliez probablement dire return string(&buf[0], size);quelque chose de similaire. Deuxièmement, si vous deviez renvoyer une chaîne c comme celle-ci, cela provoquerait un comportement indéfini car le vecteur qui contient les valeurs vers lesquelles vous pointez sera invalidé au retour. Troisièmement, lorsque j'ai commencé à apprendre le C ++, la norme ne définissait pas dans quel ordre les éléments devaient être stockés dans un std::vector, donc accéder à son stockage via un pointeur était un comportement indéfini. Maintenant, cela fonctionnerait, mais je ne vois aucun avantage à le faire de cette façon.
iFreilicht
2
@iFreilicht Un nouveau std::stringsera construit à partir du vecteur implicitement converti ( initialisation de la copie ), qui est ensuite renvoyé sous forme de copie, comme le suggère la signature de la fonction. De plus, les éléments d'un std::vectorsont, et ont toujours été destinés à être stockés de manière contiguë . Mais je comprends votre point de vue que cela pourrait ne présenter aucun avantage.
moooeeeep
4
J'aime vraiment cette solution, mais je pense que la ligne return string(buf.get(), buf.get() + size);devrait être return string(buf.get(), buf.get() + size - 1);sinon vous obtenez une chaîne avec un caractère nul à la fin. J'ai trouvé que c'était le cas sur gcc 4.9.
Phil Williams,
3
Passer une chaîne std :: à% s provoque une erreur de compilation ( erreur: impossible de passer un objet de type non trivial 'std :: __ cxx11 :: basic_string <char>' via la fonction variadic; l'appel sera abandonné à l'exécution [-Wnon-pod -varargs] ) dans clang 3.9.1, mais dans CL 19 il compile finement et plante à l'exécution. Tout indicateur d'avertissement que je peux activer pour que cela cesse au moment de la compilation en cl aussi?
Zitrax
241

Solution C ++ 11 qui utilise en vsnprintf()interne:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

Une approche plus sûre et plus efficace (je l'ai testée, et c'est plus rapide):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

La fmt_strvaleur est passée par valeur pour se conformer aux exigences de va_start.

REMARQUE: La version "plus sûre" et "plus rapide" ne fonctionne pas sur certains systèmes. Par conséquent, les deux sont toujours répertoriés. En outre, "plus rapide" dépend entièrement de l'étape de préallocation correcte, sinon le ralentit strcpy.

Erik Aronesty
la source
3
lent. pourquoi augmenter la taille de 1? Et quand cette fonction renvoie-t-elle -1?
0xDEAD BEEF
27
Vous écrasez str.c_str ()? N'est-ce pas dangereux?
quantum
8
va_start avec un argument de référence a des problèmes sur MSVC. Il échoue silencieusement et renvoie des pointeurs à la mémoire aléatoire. Pour contourner ce problème, utilisez std :: string fmt au lieu de std :: string & fmt, ou écrivez un objet wrapper.
Steve Hanov
6
Je +1, car je sais que cela fonctionnera probablement en fonction de la façon dont la plupart des chaînes std :: sont implémentées, mais c_str n'est pas vraiment destiné à être un endroit pour modifier la chaîne sous-jacente. Son censé être en lecture seule.
Doug T.
6
Et pour obtenir la longueur de chaîne résultante à l'avance, voir: stackoverflow.com/a/7825892/908336 Je ne vois pas l'intérêt d'augmenter sizeà chaque itération, quand vous pouvez l'obtenir par le premier appel de vsnprintf().
Massood Khaari
107

boost::format() fournit les fonctionnalités que vous souhaitez:

À partir du synopsis des bibliothèques de formats Boost:

Un objet format est construit à partir d'une chaîne de format, puis reçoit des arguments via des appels répétés à l'opérateur%. Chacun de ces arguments est ensuite converti en chaînes, qui sont à leur tour combinées en une seule chaîne, selon la chaîne de format.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"
kennytm
la source
5
vous pouvez également élaguer les bibliothèques dont vous avez besoin. Utilisation d'un outil fourni.
Hassan Syed
7
Le format Boost est non seulement grand, mais aussi très lent. Voir zverovich.net/2013/09/07/… et boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut
14
Inclure le boost n'importe où dans votre projet augmente immédiatement les temps de compilation. Pour les grands projets, cela n'a probablement pas d'importance. Pour les petits projets, le boost est un frein.
quant_dev
2
@vitaut Bien qu'il soit terriblement consomme beaucoup de ressources par rapport aux solutions de rechange. À quelle fréquence formatez-vous des chaînes? Considérant que cela ne prend que quelques micro secondes et que la plupart des projets ne l'utilisent probablement que quelques dizaines de fois, cela n'est pas perceptible dans un projet qui ne se concentre pas fortement sur le formatage des chaînes, non?
AturSams
2
Malheureusement, boost :: format ne fonctionne pas de la même manière: n'accepte pas les var_args. Certaines personnes aiment que tout le code lié à un seul programme soit identique / utilise les mêmes idiomes.
xor007
88

C ++ 20 inclura std::formatce qui ressemble sprintfen termes d'API mais est entièrement sûr pour le type, fonctionne avec les types définis par l'utilisateur et utilise la syntaxe de chaîne de format de type Python. Voici comment vous pourrez le formater std::stringet l'écrire dans un flux:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

ou

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

Alternativement, vous pouvez utiliser la bibliothèque {fmt} pour formater une chaîne et l'écrire dans stdoutou un flux de fichiers en une seule fois:

fmt::print(f, "Look, a string: {}", s); // where f is a file stream

En ce qui concerne la sprintfplupart des autres réponses ici, malheureusement, elles utilisent des varargs et sont intrinsèquement dangereuses à moins que vous n'utilisiez quelque chose comme l' formatattribut de GCC qui ne fonctionne qu'avec des chaînes de format littérales. Vous pouvez voir pourquoi ces fonctions ne sont pas sûres dans l'exemple suivant:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

string_formatest une implémentation de la réponse d'Erik Aronesty. Ce code se compile, mais il se bloquera très probablement lorsque vous essayez de l'exécuter:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Avertissement : je suis l'auteur de {fmt} et C ++ 20 std::format.

vitaut
la source
À mon humble avis, vous manquez l'inclusion error: 'fmt' has not been declared
Sérgio
Ce n'est qu'un extrait, pas un code complet. Évidemment, vous devez inclure <fmt / format.h> et mettre le code dans une fonction.
vitaut
pour moi, ce n'est pas si évident, à mon humble avis, vous devriez l'inclure dans l'extrait, merci pour les commentaires
Sérgio
1
Une fmtimplémentation similaire a été ajoutée à C ++ 20! stackoverflow.com/a/57286312/895245 fmt réclame actuellement le support pour cela. Super travail!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
@vitaut Merci pour votre travail là-dessus!
Curt Nichols
18

Si vous ne voulez qu'une syntaxe de type printf (sans appeler printf vous-même), jetez un œil au format Boost .

Timo Geusch
la source
Ajouter une bibliothèque entière pour une chose aussi simple n'est pas nécessaire. Cela a été répondu à stackoverflow.com/questions/19009094/… .
Douglas Daseeco
15

J'ai écrit le mien en utilisant vsnprintf pour qu'il renvoie une chaîne au lieu d'avoir à créer mon propre tampon.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

Vous pouvez donc l'utiliser comme

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
Piti Ongmongkolkul
la source
Cela fait une copie supplémentaire complète des données, il est possible d'utiliser vsnprintfdirectement dans la chaîne.
Mooing Duck
1
Utilisez le code dans stackoverflow.com/a/7825892/908336 pour obtenir au préalable la longueur de chaîne résultante. Et vous pouvez utiliser des pointeurs intelligents pour un code d'exception:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari
Je ne suis pas sûr que ce soit correct dans le cas de secours; Je pense que vous devez faire un va_copy de vl pour le deuxième vsnprintf () pour voir les arguments correctement. Pour un exemple, voir: github.com/haberman/upb/blob/…
Josh Haberman
15

Afin de formater std::stringde manière «sprintf», appelez snprintf(arguments nullptret 0) pour obtenir la longueur de tampon nécessaire. Écrivez votre fonction en utilisant un modèle variadic C ++ 11 comme ceci:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return str;
}

Compilez avec le support C ++ 11, par exemple dans GCC: g++ -std=c++11

Usage:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
user2622016
la source
std :: snprintf n'est pas disponible dans VC ++ 12 (Visual Studio 2013). Remplacez-le par _snprintf à la place.
Shital Shah
pourquoi vous n'utilisez pas à la char buf[length + 1];place de char* buf = new char[length + 1];?
Behrouz.M
La différence entre utiliser char[]et char*avec new, c'est que dans le premier cas buf serait alloué sur la pile. C'est correct pour les petits tampons, mais comme nous ne pouvons pas garantir la taille de la chaîne résultante, il est légèrement préférable de l'utiliser new. Par exemple, sur ma machine string_sprintf("value: %020000000d",5), imprimez un nombre scandaleux de zéros non significatifs avant le numéro 5, les new char[length + 1]
vidages de mémoire
idée très intelligent pour obtenir la taille de buff réelle nécessaire pour la sortie formatée
Chris
1
@ user2622016: Merci pour la solution! Veuillez noter que std::move c'est superflu .
Mihai Todor
14

[edit: 20/05/25] mieux encore ...:
Dans l'en-tête:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

La PRINTSTRING(r)fonction est de répondre à l'interface graphique ou au terminal ou à tout besoin de sortie spécial en utilisant #ifdef _some_flag_, la valeur par défaut est:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[edit '17 / 8/31] Ajout d'une version de modèle variadic 'vtspf (..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

qui est en fait une version délimitée par des virgules (à la place) des <<opérateurs parfois gênants, utilisés comme ceci:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[modifier] Adapté pour utiliser la technique dans la réponse d'Erik Aronesty (ci-dessus):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[réponse précédente]
Une réponse très tardive, mais pour ceux qui, comme moi, aiment la méthode «sprintf»: j'ai écrit et j'utilise les fonctions suivantes. Si vous l'aimez, vous pouvez étendre les options% pour mieux correspondre à celles du sprintf; ceux qui s'y trouvent actuellement sont suffisants pour mes besoins. Vous utilisez stringf () et stringfappend () comme vous le feriez pour sprintf. N'oubliez pas que les paramètres de ... doivent être de type POD.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}
slashmais
la source
@MooingDuck: paramètre de fonction modifié selon le commentaire de Dan à la réponse d'Aronesty. J'utilise uniquement Linux / gcc, et avec fmtcomme référence cela fonctionne très bien. (Mais je suppose que les gens voudront jouer avec des jouets, alors ...) S'il y a d'autres supposés «bugs», pourriez-vous s'il vous plaît nous expliquer?
slashmais
J'ai mal compris comment une partie de son code fonctionnait et je pensais que cela faisait de nombreux redimensionnements. Un nouvel examen montre que je me suis trompé. Votre code est correct.
Mooing Duck
S'appuyer sur la réponse d'Erik Aronesty est un hareng rouge. Son premier exemple de code n'est pas sûr et son second est inefficace et maladroit. L'implémentation propre est clairement indiquée par le fait que, si le buf_siz de l'une des fonctions de la famille vprintf est nul, rien n'est écrit et le tampon peut être un pointeur nul, mais la valeur de retour (nombre d'octets qui seraient écrits sans inclure le terminateur nul) est toujours calculé et renvoyé. Une réponse de qualité de production est ici: stackoverflow.com/questions/2342162/…
Douglas Daseeco
10

Voici comment Google le fait: StringPrintf(Licence BSD)
et Facebook le fait de manière assez similaire: StringPrintf(Licence Apache)
Les deux offrent également un moyen pratique StringAppendF.

PW.
la source
10

Mes deux cents sur cette question très populaire.

Pour citer la page de printfmanuel des fonctions similaires :

Lors d'un retour réussi, ces fonctions renvoient le nombre de caractères imprimés (à l'exclusion de l'octet nul utilisé pour terminer la sortie vers les chaînes).

Les fonctions snprintf () et vsnprintf () n'écrivent pas plus de taille octets (y compris l'octet nul final ('\ 0')). Si la sortie a été tronquée en raison de cette limite, la valeur de retour est le nombre de caractères (à l'exclusion de l'octet nul final) qui auraient été écrits dans la chaîne finale si suffisamment d'espace avait été disponible. Ainsi, une valeur de retour de taille ou plus signifie que la sortie a été tronquée.

En d'autres termes, une implémentation saine de C ++ 11 devrait être la suivante:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}

Ça marche plutôt bien :)

Les modèles Variadic sont pris en charge uniquement en C ++ 11. La réponse de pixelpoint montre une technique similaire utilisant des styles de programmation plus anciens.

C'est bizarre que C ++ n'ait pas une telle chose hors de la boîte. Ils ont récemment ajouté to_string(), ce qui, à mon avis, est un grand pas en avant. Je me demande s'ils ajouteront un .formatopérateur à la std::stringfin ...

Éditer

Comme l'a souligné alexk7, A +1est nécessaire sur la valeur de retour de std::snprintf, car nous devons avoir de l'espace pour l' \0octet. Intuitivement, sur la plupart des architectures manquantes +1, l' requiredentier sera partiellement remplacé par a 0. Cela se produira après l'évaluation de requiredcomme paramètre réel pour std::snprintf, donc l'effet ne devrait pas être visible.

Ce problème pourrait cependant changer, par exemple avec l'optimisation du compilateur: que faire si le compilateur décide d'utiliser un registre pour la requiredvariable? C'est le genre d'erreurs qui entraînent parfois des problèmes de sécurité.

Dacav
la source
1
snprintf ajoute toujours un octet nul final mais renvoie le nombre de caractères sans lui. Ce code ne saute-t-il pas toujours le dernier caractère?
alexk7
@ alexk7, Nice catch! Je mets à jour la réponse. Le code ne saute pas le dernier caractère, mais écrit au-delà de la fin du bytestampon, probablement sur l' requiredentier (qui, heureusement à ce moment-là, est déjà évalué).
Dacav
1
Juste un petit indice: avec une taille de tampon de 0, vous pouvez passer un nullptrcomme argument de tampon, éliminant la char b;ligne dans votre code. ( Source )
iFreilicht
@iFreilicht, corrigé. Aussi +1
Dacav
2
L'utilisation de "char bytes [required]" sera allouée sur la pile au lieu du tas, cela peut être dangereux sur les chaînes de grand format. Envisagez d'utiliser plutôt un nouveau. Yann
Yannuth
9
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

Utilisation de C99 snprintf et C ++ 11

aaa
la source
9

Testé, réponse de qualité de production

Cette réponse traite le cas général avec des techniques conformes aux normes. La même approche est donnée en exemple sur CppReference.com en bas de leur page. Contrairement à leur exemple, ce code correspond aux exigences de la question et est testé sur le terrain dans les applications de robotique et de satellite. Il a également amélioré les commentaires. La qualité de la conception est discutée plus loin ci-dessous.

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

Efficacité linéaire prévisible

Deux passes sont nécessaires pour une fonction réutilisable sécurisée, fiable et prévisible selon les spécifications de la question. Les présomptions sur la distribution des tailles de vargs dans une fonction réutilisable sont un mauvais style de programmation et doivent être évitées. Dans ce cas, des représentations de longueur variable arbitrairement grandes de vargs est un facteur clé dans le choix de l'algorithme.

Réessayer en cas de dépassement est exponentiellement inefficace, ce qui est une autre raison discutée lorsque le comité des normes C ++ 11 a discuté de la proposition ci-dessus de fournir un essai à vide lorsque le tampon d'écriture est nul.

Dans la mise en œuvre prête pour la production ci-dessus, le premier cycle est un tel cycle à sec pour déterminer la taille d'allocation. Aucune allocation n'a lieu. L'analyse des directives printf et la lecture des variables ont été rendues extrêmement efficaces au cours des décennies. Le code réutilisable doit être prévisible, même si une petite inefficacité pour les cas triviaux doit être sacrifiée.

Sécurité et fiabilité

Andrew Koenig a déclaré à un petit groupe d'entre nous après sa conférence lors d'un événement à Cambridge, "Les fonctions utilisateur ne devraient pas s'appuyer sur l'exploitation d'un échec pour des fonctionnalités exceptionnelles." Comme d'habitude, sa sagesse a été confirmée dans le dossier depuis. Les problèmes de bogues de sécurité fixes et fermés indiquent souvent des tentatives de piratage dans la description du trou exploité avant la correction.

Ceci est mentionné dans la proposition de révision des normes formelles pour la fonction de tampon nul dans Alternative to sprintf, proposition de révision C9X , document ISO CEI WG14 N645 / X3J11 96-008 . Une chaîne arbitrairement longue insérée par directive d'impression, "% s", dans les limites de la disponibilité de la mémoire dynamique, ne fait pas exception et ne doit pas être exploitée pour produire une "fonctionnalité non exceptionnelle".

Considérez la proposition à côté de l'exemple de code donné au bas de la page C ++ Reference.org liée au premier paragraphe de cette réponse.

De plus, le test des cas d'échec est rarement aussi robuste que les cas de réussite.

Portabilité

Tous les principaux fournisseurs de systèmes d'exploitation fournissent des compilateurs qui prennent entièrement en charge std :: vsnprintf dans le cadre des normes c ++ 11. Les hôtes exécutant des produits de fournisseurs qui ne maintiennent plus de distributions doivent être fournis avec g ++ ou clang ++ pour de nombreuses raisons.

Utilisation de la pile

L'utilisation de la pile lors du 1er appel à std :: vsnprintf sera inférieure ou égale à celle du 2e, et elle sera libérée avant le début du 2e appel. Si le premier appel dépasse la disponibilité de la pile, std :: fprintf échouera également.

Douglas Daseeco
la source
Bref et robuste. Il pourrait échouer sur HP-UX, IRIX, Tru64 qui ont des vsnprintf-s non conformes. EDIT: également, compte tenu de l'impact des deux passes sur les performances, en particulier. pour la mise en forme de petites chaînes la plus courante, avez-vous envisagé une supposition pour la passe initiale, qui pourrait être suffisamment grande?
Ingénieur
FWIW, la supposition à laquelle je faisais référence utilise un tampon alloué par pile où se produit la première exécution. S'il s'adapte, il économise le coût d'une deuxième exécution et l'allocation dynamique qui s'y produit. Vraisemblablement, les petites chaînes sont plus fréquemment utilisées que les grandes chaînes. Dans mon indice de référence brut, cette stratégie réduit (presque) de moitié le temps d'exécution des petites chaînes et se situe à quelques pour cent (frais généraux fixes peut-être?) De la stratégie ci-dessus. Pourriez-vous développer la conception C ++ 11 qui utilise un fonctionnement à sec, etc.? J'aimerais en lire davantage.
Ingénieur
@Engineerist, vos questions ont été traitées dans le corps de la réponse, au-dessus et en dessous du code. Les sous-rubriques peuvent être rendues plus faciles à lire de cette façon.
Douglas Daseeco
6

C ++ 20 std::format

C'est arrivé! La fonctionnalité est décrite sur: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html et utilise une .format()syntaxe de type Python .

J'espère que l'utilisation sera comme:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

Je vais essayer quand le support arrivera à GCC, GCC 9.1.0 g++-9 -std=c++2ane le supporte toujours pas.

L'API ajoutera un nouvel en- std::formattête:

L'API de formatage proposée est définie dans le nouvel en-tête <format>et ne devrait avoir aucun impact sur le code existant.

La fmtbibliothèque existante prétend l'implémenter si vous avez besoin du polyfill: https://github.com/fmtlib/fmt

Implémentation de C ++ 20 std::format.

et a été mentionné précédemment à: std :: string formating like sprintf

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
5

Sur la base de la réponse fournie par Erik Aronesty:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}

Cela évite d'avoir à rejeter constle résultat .c_str()qui figurait dans la réponse d'origine.

ChetS
la source
1
S'appuyer sur la réponse d'Erik Aronesty est un hareng rouge. Son premier exemple de code n'est pas sûr et son second, avec la boucle est inefficace et maladroit. L'implémentation propre est clairement indiquée par le fait que, si le buf_siz de l'une des fonctions de la famille vprintf est nul, rien n'est écrit et le tampon peut être un pointeur nul, mais la valeur de retour (nombre d'octets qui seraient écrits sans inclure le terminateur nul) est toujours calculé et renvoyé. Une réponse de qualité de production est ici: stackoverflow.com/questions/2342162/…
Douglas Daseeco
La réponse d'Erik Aronesty a été modifiée depuis l'ajout de la mienne. Je voulais souligner la possibilité d'utiliser le vecteur <char> pour stocker les chaînes au fur et à mesure de leur construction. J'utilise souvent cette technique lors de l'appel de fonctions C à partir de code C ++. Il est intéressant de noter que la question compte désormais 34 réponses.
ChetS
L'exemple cppreference.com sur la page vfprintf a été ajouté ultérieurement. Je crois que la meilleure réponse est la réponse actuellement acceptée, en utilisant des flux de chaînes au lieu d'une variante printf, c'est la façon de faire C ++. Cependant, ma réponse a ajouté de la valeur lorsqu'elle a été fournie; C'était progressivement mieux que les autres réponses à l'époque. Maintenant, la norme a string_view, des packs de paramètres et un modèle Variadic, une nouvelle réponse pourrait inclure ces fonctionnalités. Quant à ma réponse, même si elle ne mérite peut-être plus de votes positifs supplémentaires, elle ne mérite pas d'être supprimée ou rejetée, donc je la laisse telle quelle.
ChetS
5
inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}
pixelpoint
la source
1
+1 pour l'idée intelligente, mais ce n'est pas très clair _vscprintf. Je pense que vous devriez développer cette réponse.
Dacav
3

string n'a pas ce dont vous avez besoin, mais std :: stringstream en a. Utilisez un flux de chaînes pour créer la chaîne, puis extrayez la chaîne. Voici une liste complète des choses que vous pouvez faire. Par exemple:

cout.setprecision(10); //stringstream is a stream like cout

vous donnera 10 décimales de précision lors de l'impression d'un double ou d'un flottant.

Hassan Syed
la source
8
ce qui ne vous donne toujours rien près du contrôle que printf vous donne ... mais c'est sympa.
Erik Aronesty
3

Vous pouvez essayer ceci:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );
EddieV223
la source
3

Si vous êtes sur un système qui a asprintf (3) , vous pouvez facilement envelopper:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}
Thomas Perl
la source
2
J'ajouterais cette ligne comme déclaration avant format, car elle indique à gcc de vérifier les types d'arguments et de donner un avertissement décent avec -Wall:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid
2
Je viens d'ajouter un appel à va_end. "si va_end n'est pas appelé avant qu'une fonction qui appelle va_start ou va_copy ne revienne, le comportement n'est pas défini." - docs
Aaron McDaid
1
Vous devez vérifier le résultat de retour de vasprintf car la valeur du pointeur n'est pas définie en cas d'échec. Donc, incluez éventuellement <new> et ajoutez: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill
Bon point, j'ai modifié la réponse en conséquence, j'ai décidé de simplement y mettre un commentaire au lieu de le faire throw std::bad_alloc();, car je n'utilise pas d'exceptions C ++ dans ma base de code, et pour les gens qui le font, ils peuvent facilement l'ajouter en fonction sur le commentaire source et votre commentaire ici.
Thomas Perl
2

C'est le code que j'utilise pour le faire dans mon programme ... Ce n'est pas compliqué, mais ça fait l'affaire ... Remarque, vous devrez ajuster votre taille le cas échéant. MAX_BUFFER pour moi est 1024.

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}
Dave
la source
4
L'initialisation de textString met déjà le tampon entier à zéro. Pas besoin de memset ...
EricSchaefer
Cela fait une copie supplémentaire complète des données, il est possible d'utiliser vsnprintfdirectement dans la chaîne.
Mooing Duck
2

Repris l'idée de la réponse de Dacav et pixelpoint . J'ai joué un peu et j'ai obtenu ceci:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

Avec sain d' esprit pratique de programmation , je crois que le code devrait être assez, mais je suis toujours ouvert à des alternatives plus sûres qui sont encore assez simple et ne nécessitant pas de C ++ 11.


Et voici une autre version qui utilise un tampon initial pour empêcher un deuxième appel vsnprintf()lorsque le tampon initial est déjà suffisant.

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(Il s'avère que cette version est juste similaire à la réponse de Piti Ongmongkolkul , seulement qu'elle n'utilise pas newet delete[], et spécifie également une taille lors de la création std::string.

L'idée ici de ne pas utiliser newet delete[]est d'impliquer l'utilisation de la pile sur le tas car elle n'a pas besoin d'appeler les fonctions d'allocation et de désallocation, mais si elle n'est pas correctement utilisée, il pourrait être dangereux de tamponner les débordements dans certains (peut-être anciens, ou peut-être simplement vulnérables). Si c'est un problème, je vous suggère fortement d'utiliser newet à la delete[]place. Notez que la seule préoccupation ici concerne les allocations comme vsnprintf()on l'appelle déjà avec des limites, donc spécifier une limite basée sur la taille allouée sur le deuxième tampon les empêcherait également.)

konsolebox
la source
2

J'utilise habituellement ceci:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

Inconvénient: tous les systèmes ne prennent pas en charge vasprint

Folkert van Heusden
la source
vasprintf est bien - mais vous devez vérifier le code retour. Sur -1, le tampon aura une valeur indéfinie. Besoin: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill
2

Ci-dessous une version légèrement modifiée de la réponse @iFreilicht, mise à jour en C ++ 14 (utilisation de la make_uniquefonction au lieu de la déclaration brute) et prise en charge des std::stringarguments (basée sur l' article de Kenny Kerr )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

Production:

i = 3, f = 5.000000, s = hello world

N'hésitez pas à fusionner cette réponse avec l'original si vous le souhaitez.

Pawel Sledzikowski
la source
1

Vous pouvez formater la sortie C ++ dans cout à l'aide du fichier d'en-tête iomanip. Assurez-vous d'inclure le fichier d'en-tête iomanip avant d'utiliser l'une des fonctions d'assistance comme setprecision, setfill, etc.

Voici un extrait de code que j'ai utilisé dans le passé pour imprimer le temps d'attente moyen dans le vecteur, que j'ai "accumulé".

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

Voici une brève description de la façon dont nous pouvons formater les flux C ++. http://www.cprogramming.com/tutorial/iomanip.html

vinkris
la source
1

Il peut y avoir des problèmes si le tampon n'est pas assez grand pour imprimer la chaîne. Vous devez déterminer la longueur de la chaîne formatée avant d'y imprimer un message formaté. Je fais mon propre assistant (testé sur Windows et Linux GCC ), et vous pouvez essayer de l'utiliser.

String.cpp: http://pastebin.com/DnfvzyKP
String.h: http://pastebin.com/7U6iCUMa

String.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();
Valdemar_Rudolfovich
la source
En ce qui concerne la ligne vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- Est-il sûr de supposer que le tampon de la chaîne a de la place pour un caractère nul final? Existe-t-il des implémentations qui n'allouent pas de taille + 1 caractères. Serait-il plus sûr de le fairedst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode
Apparemment, la réponse à mon commentaire précédent est: Non, il n'est PAS sûr de supposer qu'il y a un caractère nul. Plus précisément en ce qui concerne la spécification C ++ 98: "L'accès à la valeur à data () + size () produit un comportement indéfini : il n'y a aucune garantie qu'un caractère nul termine la séquence de caractères pointée par la valeur renvoyée par cette fonction. Voir chaîne :: c_str pour une fonction qui offre une telle garantie. Un programme ne doit modifier aucun des caractères de cette séquence. "Cependant, la spécification C ++ 11 indique cela dataet c_strsont des synonymes.
drwatsoncode
1
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
user5685202
la source
1

Solution très très simple.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
Pacha
la source
1

Je réalise que cela a été répondu plusieurs fois, mais c'est plus concis:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

exemple:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

Voir aussi http://rextester.com/NJB14150

Patrick Beard
la source
1

MISE À JOUR 1 : ajout de fmt::formattests

J'ai mené ma propre enquête sur les méthodes introduites ici et j'ai obtenu des résultats diamétralement opposés par rapport à ceux mentionnés ici.

J'ai utilisé 4 fonctions sur 4 méthodes:

  • fonction variadique + vsnprintf+std::unique_ptr
  • fonction variadique + vsnprintf+std::string
  • fonction de modèle variadique + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfonction de la fmtbibliothèque

Pour le backend de test, le googletesta utilisé.

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

L' for_eachimplémentation est prise à partir d'ici: itérer sur tuple

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

Les tests:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

Le UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unsued.hpp :

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

used.cpp :

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

RÉSULTATS :

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

Comme vous pouvez le voir, l'implémentation via le vsnprintf+ std::stringest égale à fmt::format, mais plus rapide que via le vsnprintf+ std::unique_ptr, qui est plus rapide que via le std::ostringstream.

Les tests compilés Visual Studio 2015 Update 3et exécutés à Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Andry
la source