Smart Tricks with Parameter Packs and Fold Expressions

Contents[Show]

To complete my post about variadic templates and fold expressions, I present in this post smart tricks using parameter packs and fold expressions.

FoldExpressions

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.

Finally, here is the output of the program. printFoldExpressions 

Thanks to fold expressions, you can also 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";

}

 

The 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 the 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.

The following screenshot shows the output of the program.

 pushBackFoldExpressions

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 layout it properly.

 

template<typename ... Ts> 
struct Overload : Ts ... { 
    using Ts::operator() ... ; 
};

 

The struct Overload can have arbitrary 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 that is 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.

overloadPattern

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 very handy into play. Read more about std::variant, std::visit, and the overload pattern in my next post.

 

Pdf Bundle: C++20 Modules

Base on the last poll, I've created the next pdf bundle.

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, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, and Michael Young.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, and Rusty Fleming.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 

 

 

 

 

 

Comments   

0 #1 Rud Merriam 2021-09-20 17:04
Overload can be used with tuples, also. It is kind of a switch for data types if you squint. Use std::apply to walk through the tuple. Article coming real soon now.
Quote

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 6429

Yesterday 7646

Week 39724

Month 106390

All 7374230

Currently are 172 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments