C++ 17 Plegado de plantillas variables
variadic-templates fold (7)
Como contestaron otros, estás tratando de usar un formato de expresión de plegado incorrecto. Podría usar un ayudante lambda para su propósito de una manera muy 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;
}
Esto seguirá el comportamiento esperado en el código que escribiste. Sin embargo, si desea evitar el sep antes del primer argumento, puede usar lo siguiente:
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;
}
No entiendo por qué esto no funciona. ¿Podría alguien que entienda las plantillas y el plegamiento de expresiones variables explicar lo que está sucediendo y dar una solución que funcione?
#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);
}
Debe imprimir cada uno de los argumentos con un espacio en el medio y una nueva línea al final. Funciona si elimina el sep <<
pero luego no hay espacio entre cada argumento cuando se imprime.
Esto funcionará, pero imprimirá un espacio al final:
template <typename... Args>
void print(Args... args)
{
std::string sep = " ";
std::string end = "/n";
((std::cout << args << sep), ...) << end;
}
ejemplo de caja de varita en vivo
En este caso, se está realizando un pliegue sobre el operador de coma , lo que resulta en una expansión como:
// (pseudocode)
(std::cout << args<0> << sep),
(std::cout << args<1> << sep),
(std::cout << args<2> << sep),
...,
(std::cout << args<N> << sep),
La gramática de las fold-expressions binarias debe ser una de las siguientes:
(pack op ... op init)
(init op ... op pack)
Lo que tienes es (std::cout << ... << sep << args)
, que no se ajusta a ninguna de las dos formas. Necesitas algo como (cout << ... << pack)
, por lo que la eliminación de sep
funciona.
En su lugar, puedes doblar una coma:
((std::cout << sep << args), ...);
o use recursion:
template <class A, class... Args>
void print(A arg, Args... args) {
std::cout << arg;
if constexpr (sizeof...(Args) > 0) {
std::cout << sep;
print(args...);
}
}
Lo que realmente quieres hacer es:
std::string sep = " ";
std::string end = "/n";
(std::cout << ... << (sep << args)) << end;
porque quiere que (sep << args)
a la izquierda con std::cout
. Esto no funciona, porque sep << args
no sabe que se está transmitiendo a std::cout
o que no se transmite; <<
solo se transmite si el lado izquierdo es un flujo.
En resumen, el problema es que sep << args
no entiende que está transmitiendo.
Tu otro problema no es suficiente lambda.
Podemos arreglar esto.
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;
}
ejemplo vivo . (También usé un literal para sep
para asegurar que trabajo con valores).
ostreamer
captura referencias a cosas que son <<
''d, y luego las descarga cuando, a su vez, es <<
a un ostream
.
Todo este proceso debería ser transparente para el compilador, por lo que un optimizador decente debería evaporar todo lo que implica.
Otro enfoque es el siguiente:
#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;
}
Uso:
printSpaced(1, 2, "Hello", 4.5f); //Output 1 2 Hello 4.5 and no trailing space
Puedes probar algo como esto
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)), ...);
}
La lambda garantiza que el separador solo se inserta entre dos elementos.
Por otro lado, si no quieres usar lambdas puedes sobrecargar la plantilla:
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 no quieres liderar / arrastrar 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;
}
Necesitas hacer std::cout << end;
una instrucción separada para manejar el caso con un parámetro.