web-dev-qa-db-fra.com

C ++ 17 Variadic Template Folding

Je ne comprends pas pourquoi cela ne fonctionne pas. Quelqu'un qui comprend les modèles et le pliage des expressions variadiques pourrait-il expliquer ce qui se passe et donner une solution qui fonctionne?

#include <iostream>
#include <string>

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << sep << args) << end;
}

int main()
{
    print(1, 2, 3);
}

Il devrait imprimer chacun des arguments avec un espace entre les deux et une nouvelle ligne à la fin. Cela fonctionne si vous supprimez le sep << mais il n'y a pas d'espace entre chaque argument lors de son impression.

25
nickeb96

La grammaire du binaire expressions-plis doit être l'une des suivantes:

(pack op ... op init)
(init op ... op pack)

Ce que vous avez est (std::cout << ... << sep << args), qui ne correspond à aucune des deux formes. Vous avez besoin de quelque chose comme (cout << ... << pack), c'est pourquoi la suppression de sep fonctionne.

Au lieu de cela, vous pouvez soit replier une virgule:

((std::cout << sep << args), ...);

ou utilisez la récursivité:

template <class A, class... Args>
void print(A arg, Args... args) {
    std::cout << arg;
    if constexpr (sizeof...(Args) > 0) {
        std::cout << sep;
        print(args...);
    }
}
32
Barry

Cela fonctionnera, mais cela imprimera un espace de fin:

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";

    ((std::cout << args << sep), ...) << end;
}

exemple de boîte à baguette en direct


Dans ce cas, un repli sur opérateur virgule est en cours, résultant en une expansion comme:

// (pseudocode)
(std::cout << args<0> << sep), 
(std::cout << args<1> << sep),
(std::cout << args<2> << sep), 
...,
(std::cout << args<N> << sep), 
15
Vittorio Romeo

Ce que vous voulez vraiment faire, c'est:

std::string sep = " ";
std::string end = "\n";
(std::cout << ... << (sep << args)) << end;

parce que tu veux (sep << args) à replier à gauche avec std::cout. Cela ne fonctionne pas, car sep << args ne sait pas qu'il est diffusé vers std::cout ou en streaming du tout; << ne diffuse que si le côté gauche est un flux.

En bref, le problème est que sep << args ne comprend pas la diffusion.

Votre autre problème n'est pas assez lambda.

Nous pouvons résoudre ce problème.

template<class F>
struct ostreamer_t {
    F f;
    friend std::ostream& operator<<(std::ostream& os, ostreamer_t&& self ) {
        self.f(os);
        return os;
    }
    template<class T>
    friend auto operator<<(ostreamer_t self, T&& t) {
        auto f = [g = std::move(self.f), &t](auto&& os)mutable {
            std::move(g)(os);
            os << t;
        };
        return ostreamer_t<decltype(f)>{std::move(f)};
    }
};

struct do_nothing_t {
    template<class...Args>
    void operator()(Args&&...)const {}
};

const ostreamer_t<do_nothing_t> ostreamer{{}};

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << (ostreamer << sep << args)) << end;
}

exemple en direct . (J'ai également utilisé un littéral pour sep pour m'assurer que je travaille avec rvalues).

ostreamer capture les références à ce que c'est << 'd, puis les vide quand à son tour c'est << à un ostream.

Tout ce processus devrait être transparent pour le compilateur, donc un optimiseur décent devrait évaporer tout ce qui est impliqué.

Comme d'autres l'ont répondu, vous essayez d'utiliser un mauvais format fold-expression. Vous pouvez utiliser un assistant lambda à vos fins de manière très simple:

template <typename... Args>
void print(Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    (std::cout << ... << streamSep(args)) << end;
}

Cela suivra le comportement attendu dans le code que vous avez écrit. Cependant, si vous souhaitez éviter le sep avant le premier argument, vous pouvez utiliser ce qui suit:

template <typename Arg, typename... Args>
void print(Arg&& arg, Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    std::cout << arg;
    (std::cout << ... << streamSep(args)) << end;
}
3
dodomorandi

Une autre approche est la suivante:

#include <iostream>

template<class U, class... T>
    void printSpaced(const U& u, const T&... args)
{   
     using std::cout;
     using std::endl;         

     ((cout << u) << ... << (cout << ' ', args)) << endl;   
}

De cette façon, vous n'obtiendrez pas d'espace de début/fin
Usage:

printSpaced(1, 2, "Hello", 4.5f); //Output 1 2 Hello 4.5 and no trailing space
2
Gerard097

Vous pouvez essayer quelque chose comme ça

template <typename... Args>
void print(Args... args)
{
  bool first = true;
  auto lambda = [&](auto param)
  {
    if( !first) std::cout << ',';
    first= false;
    return param;
  };

  ((std::cout << lambda(args)), ...);
}

Le lambda garantit que le séparateur n'est inséré qu'entre deux éléments.

D'un autre côté, si vous ne voulez pas utiliser de lambdas, vous pouvez surcharger le modèle:

template<typename T>
void print(T item)
{
  std::cout << item;
}

template<typename T, typename... Args>
void print(T item, Args... args)
{
  print(item);
  std::cout << ',';
  print(args...);
}
1
eferion

Si vous ne voulez pas de début/fin sep:

template <typename First, typename... Rest>
void print(First first, Rest... rest)
{
    std::string sep = " ";
    std::string end = "\n";

    std::cout << first;
    ((std::cout << sep << rest), ...);
    std::cout << end;
}

Vous devez faire std::cout << end; une instruction séparée pour gérer la casse avec un paramètre.

1
Meloman