Quelle est la façon la plus simple d'imprimer un pack de paramètres, séparés par des virgules, en utilisant std::ostream
?
Exemple:
template<typename... Args>
void doPrint(std::ostream& out, Args... args){
out << args...; // WRONG! What to write here?
}
// Usage:
int main(){
doPrint(std::cout,34,"bla",15); // Should print: 34,bla,15
}
Remarque: On peut supposer qu'une surcharge correspondante de l'opérateur <<
Est disponible pour tous les types de pack de paramètres.
Sans appels récursifs ni virgules où vous vouliez.
Dans c ++ 11 / c ++ 14 via l'extension du pack de paramètres:
template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
out << std::forward<Arg>(arg);
using expander = int[];
(void)expander{0, (void(out << ',' << std::forward<Args>(args)), 0)...};
}
Dans c ++ 17 en utilisant des expressions de repli:
template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
out << std::forward<Arg>(arg);
((out << ',' << std::forward<Args>(args)), ...);
}
En C++ 17, il y aura un moyen plus simple (comme laissé entendre par Kerrek SB dans les commentaires; il était en fait présent dans N4606, le premier brouillon post-C++ 14), appelé ( expressions de pli :
Le code serait:
(out << ... << args);
et le motif expression op
...
op parameter-pack
est appelé un pli gauche binaire , dont la définition est équivalente à (((
expression
op arg1) op arg2) op arg3)
.... op argN
.
Je pense que les parenthèses externes ne sont pas strictement nécessaires pour une expression-expression comme celle-ci, mais si l'expression de repli est un opérande d'un autre opérateur, elles sont soit obligatoires, soit une très bonne idée :)
La réponse habituelle consiste à définir deux surcharges distinctes, avec une surcharge vide pour le cas de base:
// base case
void doPrint(std::ostream& out) {}
template <typename T, typename... Args>
void doPrint(std::ostream& out, T t, Args... args)
{
out << t; // add comma here, see below
doPrint(out, args...);
}
Bien sûr, dans le code réel, je ne ferais pas de copies des arguments à chaque fois et utiliserais plutôt des références de transfert, mais vous avez l'idée.
Si vous souhaitez ajouter des virgules après chaque élément, même après le dernier, remplacez simplement out << t
avec out << t << ','
.
Si vous ne voulez que des virgules à l'intérieur, pas au-delà du dernier élément, vous avez besoin d'une surcharge séparée à un argument qui n'imprime pas la virgule, et une surcharge générique prend deux arguments distincts avant le pack, à savoir:
template <typename T>
void doPrint(std::ostream& out, T t)
{
out << t;
}
template <typename T, typename U, typename... Args>
void doPrint(std::ostream& out, T t, U u, Args... args)
{
out << t << ',';
doPrint(out, u, args...);
}
L'expansion du pack de paramètres ne fonctionne que dans les appels de fonction simples, pas pour les opérateurs d'infixe. Par conséquent, vous devez convertir la syntaxe s << x
En syntaxe d'appel de fonction simple f(s, x)
:
template<class Head>
void print_args_(std::ostream& s, Head&& head) {
s << std::forward<Head>(head);
}
template<class Head, class... Tail>
void print_args_(std::ostream& s, Head&& head, Tail&&... tail) {
s << std::forward<Head>(head);
print_args_(s, std::forward<Tail>(tail)...);
}
template<class... Args>
void print_args(Args&&... args) {
print_args_(std::cout, std::forward<Args>(args)...);
}
Je sais que c'est une vieille question, mais cela m'a beaucoup aidé avec mon problème. J'ai créé une classe utilitaire basée sur les réponses de ce post et je voudrais partager mon résultat.
Étant donné que nous utilisons C++ 11 ou les dernières versions C++, cette classe fournit des fonctions print et println pour composer des chaînes avant d'appeler le flux de sortie standard et d'éviter les problèmes de concurrence. Ce sont des fonctions variadiques qui utilisent des modèles pour imprimer différents types de données.
Vous pouvez vérifier son utilisation dans un problème producteur-consommateur sur mon github: https://github.com/eloiluiz/threadsBar
Alors, voici mon code:
class Console {
private:
Console() = default;
inline static void innerPrint(std::ostream &stream) {}
template<typename Head, typename... Tail>
inline static void innerPrint(std::ostream &stream, Head const head, Tail const ...tail) {
stream << head;
innerPrint(stream, tail...);
}
public:
template<typename Head, typename... Tail>
inline static void print(Head const head, Tail const ...tail) {
// Create a stream buffer
std::stringbuf buffer;
std::ostream stream(&buffer);
// Feed input parameters to the stream object
innerPrint(stream, head, tail...);
// Print into console and flush
std::cout << buffer.str();
}
template<typename Head, typename... Tail>
inline static void println(Head const head, Tail const ...tail) {
print(head, tail..., "\n");
}
};
J'aime mieux cette alternative que de surcharger le <<
ou en utilisant des fonctions de flux complexes. C'est une approche récursive, mais pas si difficile à comprendre.
Le formulaire générique qui fonctionne avec std::wostream
ainsi que:
template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out)
{
return out;
}
template <typename CharT, typename Traits, typename T>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t)
{
return (out << std::forward<T>(t));
}
template <typename CharT, typename Traits, typename T, typename... Args>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t, Args &&...args)
{
return Print( Print(out, std::forward<T>(t)), std::forward<Args>(args)... );
}
Je ne pouvais pas le faire fonctionner avec std::endl
dans le cas courant (il est possible de gérer std::endl
dans des cas spécifiques comme quand c'est le premier ou le dernier argument, mais pas dans le cas commun, surtout s'il y a plusieurs std::endl
en un seul appel). Vous pouvez toujours utiliser '\n'
à la place ou utilisez std::endl
avec des arguments de modèle spécifiés si vous avez vraiment besoin de std::endl
:
Print(std::cout, "hello world", std::endl<char, std::char_traits<char>>);
Les différences entre std::endl
et '\n'
'\n'
n'est pas converti au format de fin de ligne de la plateforme pour laquelle le code a été compilé (mais en mode texte, il est toujours converti).'\n'
ne vide pas le flux avec std::flush
(mais il reste flushes le std::cout
si le programme s'exécute sur un terminal)Donc pour moi, c'est OK d'utiliser '\n'
, ou peut-être même préféré .
L'utilisation d'autres manipulateurs IO est toujours possible:
Print(std::cout, std::hex, 11, '\n');
J'ai également implémenté sprintf
homologue qui fonctionne avec des modèles variadiques et renvoie std::string
:
template <typename CharT = char, typename Traits = std::char_traits<CharT>, typename... Args>
std::basic_string<CharT, Traits>
SPrint(Args &&...args)
{
std::basic_stringstream<CharT, Traits> ss;
Print(ss, std::forward<Args>(args)...);
return std::move(ss.str());
}
Voici quelques démos .