C++ Core Guidelines: Rules for Variadic Templates

Contents[Show]

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

 

theater 311730 1280

Before I write about the details to variadic temples, I want to make a short remark about my introduction to this post. 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 deeper than in other mainstream programming languages such as Python, Java, or even 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 it to provide easy to use (difficult 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 rules.

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 gets 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 in line (3) an lvalue. The last one even gets an rvalue und an lvalue.
  3. It should forward it 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.

This requirements are typically for factory functions such as std::make_unique, std::make_shared, std::make_tuple, but also 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.

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 important. 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 a ellipse. By using them Args, or args becomes 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 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 place 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 which can get an arbitraty 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 case of line (2), the function template (line 4) is applied. The function templates loops (line 5) as long as the format symbol is not equal to `\0`. If the format symbol is not equal to `\0` , two control flows are possible. 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, if the format string does not start with '%', the format symbol is just displayed (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. Afterwards, the guidelines continue with template metaprogramming. I'm not sure how deep I should dive into template metaprogramming in my next post.

 

 

 

 

Thanks a lot to my Patreon Supporters: Eric Pederson, Paul Baxter,  Meeting C++, Matt Braun, Avi Lachmish, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Dilettant, Marko, Ramesh Jangama, and Emyr Williams.

 

Thanks in particular to:  TakeUpCode 450 60

 

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more the 100 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 600 pages full of modern C++ and more than 100 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

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

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations
Tags: templates

Add comment


Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 3889

All 2420682

Currently are 132 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments