web-dev-qa-db-fra.com

Quelle est la façon la plus simple d'imprimer un pack de paramètres variadic en utilisant std :: ostream?

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.

27
gexicide

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)...};
}

[~ # ~] démo [~ # ~]


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)), ...);
}

DEMO 2

38
Piotr Skotnicki

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 à (((expressionop 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 :)

16
M.M

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...);
}
8
Kerrek SB

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)...);
}
6
Maxim Egorushkin

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.

1
eloiluiz

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'

  • Si le flux fonctionne en mode binaire, alors '\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 .

1
anton_rh