c++ variadic-templates fold c++17

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.