C++ Core Guidelines: Rules for Variadic Templates

Contents[Show]

Variadic templates are a typical feature of C++: from the user's perspective, they are easy to use, but from the implementor's perspective, they look pretty scary. Today's post is mainly about the implementor's perspective.

 

theater 311730 1280

Before I write about the details of variadic temples, I want to mention my introduction to this post briefly. I often wear two heads when I teach C++: one for the user and one for the implementor. Features such as templates are easy to use but challenging to implement. This significant gap is typically for C++ and, I assume, more profound than in other mainstream programming languages such as Python, Java, or C. Honestly, I have no problem with this gap. I call this gap abstraction, and it is an essential part of the power of C++. The art of the implementer of the library or framework is to provide easy-to-use (complicated to misuse) and stable interfaces.  If you don't get the point, wait for the next section, when I develop std::make_unique.

Today's post is based on three rules:

You can already guess it. The three rules are title-only; therefore, I make one story out of the first three.

As promised, I want to develop std::make_unique. std::make_unique is a function template that returns a dynamically allocated object protected by a std::unique_ptr. Let me show you a few use cases.

// makeUnique.cpp

#include <memory>

struct MyType{
    MyType(int, double, bool){};
};

int main(){
    
    int lvalue{2020};
    
    std::unique_ptr<int> uniqZero = std::make_unique<int>();      // (1)
    auto uniqEleven = std::make_unique<int>(2011);                // (2)
    auto uniqTwenty = std::make_unique<int>(lvalue);              // (3)
    auto uniqType = std::make_unique<MyType>(lvalue, 3.14, true); // (4)
    
}

 

Based on this use case, what are the requirements of std::make_unique?

  1. It should deal with an arbitrary number of arguments. The std::make_unique calls get 0, 1, and 3 arguments.
  2. It should deal with lvalues and rvalues. The std::make_unique call in line (2) gets an rvalue and an lvalue in line (3). The last one even gets an rvalue and an lvalue.
  3. It should forward its arguments unchanged to the underlying constructor. This means the constructor of std::unique_ptr should get an lvalue/rvalue if std::make_unique gets an lvalue/rvalue.

These requirements are typically for factory functions such as std::make_unique, std::make_shared, std::make_tuple, and std::thread. Both rely on two powerful features of C++11:

  1. Variadic templates
  2. Perfect forwarding

Now, I want to create my factory function createT. Let me start with perfect forwarding.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

Perfect Forwarding

First of all: What is perfect forwarding?

  • Perfect forwarding allows you to preserve an argument’s value category (lvalue/rvalue) and const/volatile modifiers.

Perfect forwarding follows a typical pattern consisting of a universal reference and std::forward.

template<typename T>        // (1)
void create(T&& t){         // (2)
    std::forward<T>(t);     // (3)
}

 

The three parts of the pattern to get perfect forwarding are:

  1. You need a template parameter T: typename T
  2. Bind T by universal reference, also known as perfect forwarding reference: T&& t
  3. Invoke std::forward on the argument: std::forward<T>(t)

The key observation is that T&& (line 2) can bind an lvalue or an rvalue and that std::forward (line 3) does the perfect forwarding.

It's time to create the prototype of the createT factory function, which should behave at the end, such as makeUnique.cpp. I just replaced std::make_unique with the createT call, added the createT factory function, and commented the lines (1) and (4) out. Additionally, I removed the header <memory> (std::make_unique) and added the header <utility>(std::foward).

 

// createT1.cpp

#include <utility>

struct MyType{
    MyType(int, double, bool){};
};

template <typename T, typename Arg>
T createT(Arg&& arg){
    return T(std::forward<Arg>(arg));
}
    
int main(){
    
    int lvalue{2020};
    
    //std::unique_ptr<int> uniqZero = std::make_unique<int>();      // (1)
    auto uniqEleven = createT<int>(2011);                // (2)
    auto uniqTwenty = createT<int>(lvalue);              // (3)
    //auto uniqType = std::make_unique<MyType>(lvalue, 3.14, true); // (4)
    
}

 

Fine. An rvalue (line 2) and an lvalue (line 3) pass my test.

Variadic Templates

Sometimes dots are essential. Putting exactly nine dots at the right place and line (1) and line (4) work.

 

// createT2.cpp

#include <utility>

struct MyType{
    MyType(int, double, bool){};
};

template <typename T, typename ... Args>
T createT(Args&& ... args){
    return T(std::forward<Args>(args) ... );
}
    
int main(){
    
    int lvalue{2020};
    
    int uniqZero = createT<int>();                       // (1)
    auto uniqEleven = createT<int>(2011);                // (2)
    auto uniqTwenty = createT<int>(lvalue);              // (3)
    auto uniqType = createT<MyType>(lvalue, 3.14, true); // (4)
    
}

 

How does the magic work? The three dots stand for an ellipse. By using them Args, or args become a parameter pack. To be more precise, Args is a template parameter pack, and args is a function parameter pack. You can only apply two operations to a parameter pack: you can pack or unpack it. If the ellipse is left of Args the parameter pack is packed; if the ellipse is right of Args the parameter pack is unpacked. In the case of the expression (std::forward<Args>(args)...) this means the expression is unpacked until the parameter pack is consumed, and a comma is just placed between the unpacked components. This was all.

CppInsight helps you to look under the curtain.

createT2 1

createT2 2

Now, I'm nearly done. Here is my createT factory function.

 

template <typename T, typename ... Args>
T createT(Args&& ... args){
    return T(std::forward<Args>(args) ... );
}

 

The two missing steps are.

  1. Create a std::unique_ptr<T> instead of a plain T
  2. Rename my function make_unique.

I'm done.

std::make_unique

 

template <typename T, typename ... Args>
std::unique_ptr<T> make_unique(Args&& ... args){
    return std::unique_ptr<T>(new T(std::forward<Args>(args) ... ));
}

 

I forgot to scare you. Here is the scary part of my post.

printf

Of course, you know the C function printf. This is its signature: int printf( const char* format, ... );. printf is a function that can get an arbitrary number of arguments. Its power is based on the macro va_arg and is, therefore, not typesafe.

Thanks to variadic templates, printf can be rewritten in a typesafe way.

// myPrintf.cpp

#include <iostream>
 
void myPrintf(const char* format){                           // (3)
    std::cout << format;
}
 
template<typename T, typename ... Args>
void myPrintf(const char* format, T value, Args ... args){   // (4)
    for ( ; *format != '\0'; format++ ) {                    // (5)
        if ( *format == '%' ) {                              // (6) 
           std::cout << value;
           myPrintf(format + 1, args ... );                  // (7)
           return;
        }
        std::cout << *format;                                // (8)
    }
}
 
int main(){
    
    myPrintf("\n");                                          // (1)
    
    myPrintf("% world% %\n", "Hello", '!', 2011);            // (2)
    
    myPrintf("\n");                                          
    
}

 

How does the code work? If myPrintf is invoked with only a format string (line 1), line (3) is used. In the case of line (2), the function template (line 4) is applied. The function templates loops (line 5) as long as the format symbol does not equal `\0`. Two control flows are possible if the format symbol is not equal to `\0`. First, if the format starts with '%'  (line 6), the first argument value is displayed, and myPrintf is once more invoked, but this time with a new format symbol and an argument less (line 7). Second, the format symbol is displayed if the format string does not start with '%' (line 8). The function myPrintf (line 3) is the end condition for the recursive calls.

The output of the program is as expected.

 myPrintf

What's next?

One rule to variadic templates is left. Afterward, the guidelines continue with template metaprogramming. I'm unsure how deep I should dive into template metaprogramming in my next post.

 

 

 

 

 

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, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, 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, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, and Rob North.

 

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

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.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

Stay Informed about my Mentoring

 

Mentoring

English 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

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

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 1992

Yesterday 4344

Week 38870

Month 19116

All 12097325

Currently are 153 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments