Smart Tricks with Parameter Packs and Fold Expressions
To complete my post about variadic templates and fold expressions, I present in this post smart tricks using parameter packs and fold expressions.
Fold expressions enable it to reduce a parameter pack with a binary operator. Thanks to them, you can write concise expressions for repeated operations. This repeated operation can be a print function or a push_back function to push elements onto a vector. Let me start with the print function.
// printFoldExpressions.cpp #include <iostream> #include <string> template<typename ... Args> void printMe(Args&& ... args) { (std::cout << ... << std::forward<Args>(args)) << '\n'; } int main() { std::cout << '\n'; std::cout << std::boolalpha; printMe(); printMe("Rainer ", "Grimm"); printMe(true, " ", "+", " ",false, " = ", true + false); std::cout << '\n'; }
The printMe
function can accept an arbitrary number of arguments. In the concrete function, this means no argument, two C-strings, and a few strings and numbers. The printMe
function automatically deduces their types and displays them. Three powerful C++ techniques are involved.
- Variadic templates (
...
): accepts an arbitrary number of arguments. Read more here: “Variadic Templates or the Power of Three Dots” and “More about Variadic Templates“. - Perfect forwarding (
std::forward
): forwards the arguments without changing their value category. Read more here: Perfect Forwarding. - Fold expressions
(std::cout << ... << std::forward<Args>(args)
): reduces the parameter pack from the left using the binary operator<<
and the initial valuestd::cout
. Read more here: From Variadic Templates to Fold Expressions.
Finally, here is the output of the program.
Thanks to fold expressions, you can push an arbitrary number of arguments onto a vector.
// pushBackFoldExpressions.cpp #include <iostream> #include <string> #include <vector> using namespace std; template<typename T, typename... Args> void myPushBack(vector<T>& v, Args&&... args) { (v.push_back(args), ...); // (1) } int main() { std::cout << '\n'; std::vector<int> myIntVec; myPushBack(myIntVec, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); for (auto v : myIntVec) std::cout << v << ' '; std::cout << "\n\n"; std::vector myDoubleVec{1.1, 2.2, 3.3}; // (2) myPushBack(myDoubleVec, 4.4, 5.5, 6.6); for (auto v: myDoubleVec) std::cout << v << ' '; std::cout << "\n\n"; }
Lines (1) and (2) are the most interesting ones. (2) pushes the three doubles onto the vector. With C++17, the compiler can automatically deduce the types of arguments. The expression (v.push_back(args),...)
pushes the elements from the right using the binary comma operator (,
). Alternatively, I could also push from the left (..., v.push_back(args))
, because the comma operator is associative. Honestly, this looks weird. Therefore, I prefer the first variant.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
The following screenshot shows the program output.
Now, I want to go one stack back from fold expressions to variadic templates and present the overload pattern. The overload pattern is a clever way to wrap multiple lambdas into an overload set.
Johnathan O’Connor called my attention to the fact that the article Nifty Fold Expressions Tricks by Jonathan Müller provides more fold tricks.
Overload Pattern
I want to make it short. Here is the overload pattern implemented with C++20:
template<typename ... Ts> struct Overload : Ts ... { using Ts::operator() ... ; };
What? Sorry, my mistake. I should lay it out properly.
template<typename ... Ts> struct Overload : Ts ... { using Ts::operator() ... ; };
The struct Overload
can have arbitrarily many base classes (Ts ...
). It derives from each class public
and brings the call operator (Ts::operator..
.) of each base class into its scope.
There is more to explain about these four magic lines of code. Before I do that in my next post, let me use the overload pattern to display the types of integral literals. The following program requires a C++20 compiler.
// overloadPattern.cpp #include <iostream> template<typename ... Ts> struct Overload : Ts ... { using Ts::operator() ...; }; int main() { std::cout << '\n'; auto TypeOfIntegral = Overload { [](int) { return " int"; }, [](unsigned int) { return " unsigned int"; }, [](long int) { return " long int"; }, [](long long int) { return "long long int"; }, [](auto) { return "unknown type"; }, }; std::cout << "TypeOfIntegral(5): " << TypeOfIntegral(5) << '\n'; std::cout << "TypeOfIntegral(5u): " << TypeOfIntegral(5u) << '\n'; std::cout << "TypeOfIntegral(5U): " << TypeOfIntegral(5U) << '\n'; std::cout << "TypeOfIntegral(5l): " << TypeOfIntegral(5l) << '\n'; std::cout << "TypeOfIntegral(5L): " << TypeOfIntegral(5L) << '\n'; std::cout << "TypeOfIntegral(5ll): " << TypeOfIntegral(5ll) << '\n'; std::cout << "TypeOfIntegral(5LL): " << TypeOfIntegral(5LL) << '\n'; std::cout << '\n'; std::cout << "TypeOfIntegral(5ul): " << TypeOfIntegral(5ul) << '\n'; std::cout << "TypeOfIntegral(5.5): " << TypeOfIntegral(5.5) << '\n'; std::cout << '\n'; }
In the program overloadPattern.cpp
, the overload set consists of lambda expressions accepting an int
, an unsigned int
, a long int
, a long long int
, and auto
. auto
is the fallback used, for example, if the overload set is invoked with an unknown type. This happens when I invoke TypeOfIntegral
with an unsigned long
or a double
value.
What’s next?
Typically, you use the overload pattern for a std::variant
. std::variant
is a type-safe union. An instance var
of std::variant
(C++17) has one value from one of its types. std::visit
allows you to apply a visitor to var
. Exactly, here comes the overload pattern convenient into play. Read more about std::variant, std::visit
, and the overload pattern in my next post.
Pdf Bundle: C++20 Modules
Based on the last poll, I’ve created the next pdf bundle.
The pdf bundle includes all
- posts.
- source code files to these posts.
Here is more info on how to get the pdf bundle: The New pdf Bundle is Ready: C++20 Modules
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!