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