C++ Core Guidelines: Surprise included with the Specialisation of Function Templates

Contents[Show]

Today, I finish the C++ core guidelines rules to templates with a big surprise for many C++ developers. I write about the specialisation of function templates.

animal 1821737 1280

Let's start simple. Here is a template specialisation from a bird-eyes perspective.

Template Specialisation

Templates define the behaviour of families of classes and functions. Often it is necessary that special types or non-types may be treated special. To support this use-case, you fully specialise templates. Class templates can even be partially specialised.

Here is a code snippet to get a general idea.

template <typename T, int Line, int Column>     // (1)
class Matrix;

template <typename T>                           // (2)
class Matrix<T, 3, 3>{};

template <>                                     // (3)
class Matrix<int, 3, 3>{};

 

Line 1 is the primary or general templates. This template must be declared at least and has to be declared before the partially or fully specialised templates. With line 2 follows the partial specialisation. Line 3 is the full specialisation. 

To better understand partially and fully specialisation, I want to present a visual explanation. Think about an n-dimensional space of template parameters. In the primary template (line 1) you can choose an arbitrary type, and two arbitrary int's. In the case of the partial specialisation in line 2, you can only choose the type. This means the 3-dimensional space is reduced to a line. Fully specialisation means that you have one point in a 3-dimensional space. 

What is happening when you invoke the templates?

 

Matrix<int, 3, 3> m1;          // class Matrix<int, 3, 3>

Matrix<double, 3, 3> m2;       // class Matrix<T, 3, 3> 

Matrix<std::string, 4, 3> m3;  // class Matrix<T, Line, Column> => ERROR

 

m1 uses the full specialisation, m2 uses the partial specialisation, and m3 the primary template which causes an error because the definition is missing.

Here are three rules which the compiler uses to get the right specialisation:

  1. The compiler finds only one specialisation. The compiler uses the specialization.
  2. The compiler finds more than one specialisation. The compiler uses the most specialised one. If this process ends in more than one specialisation, the compiler throws an error.
  3. The compiler finds no specialisation. It uses the primary specialisation.

Okay, I have to explain what A is a more specialised template than B means. Here is the informal defintion of cppreference.com: "A accepts a subset of the types that B accepts".

After the first overview, I can dig a little bit deeper into function templates

Specialisation and Overloading of Function Templates

Function templates makes the job of template specialisation easier but also more difficult at the same time.

  • Easier, because function template only supports full specialisation.
  • More difficult, because function overloading comes into play.

From the design perspective, you can specialise a function template with template specialisation or overloading.

 

// functionTemplateSpecialisation.cpp

#include <iostream>
#include <string>

template <typename T>             // (1)
std::string getTypeName(T){
    return "unknown type";
}

template <>                       // (2)
std::string getTypeName<int>(int){
    return "int";
}

std::string getTypeName(double){  // (3)
    return "double";
}

int main(){
    
    std::cout << std::endl;
    
    std::cout << "getTypeName(true): " << getTypeName(true) << std::endl;
    std::cout << "getTypeName(4711): " << getTypeName(4711) << std::endl;
    std::cout << "getTypeName(3.14): " << getTypeName(3.14) << std::endl;
    
    std::cout << std::endl;
    
}

Line 1 has the primary  template, line 2 the full specialisation for int, and line 3 the overload for double. Because I'm not interested in the values for the function or function templates, I skipped them: std::string getTypeName(double) for example. The usage of the various functions is quite comfortable. The compiler deduces the types and the correct function or function template is invoked. In case of the function overloading the compiler prefers the function overloading to the function template when the function overloading is a perfect fit.

functionTemplateSpecialisationBut, where is the big surprise, I mentioned in the title of my post? Here it is.

T.144: Don’t specialize function templates

The reason for the rules is quite short: function template specialisation don't participate in overloading. Let's see what that means. My program is based on the program snippet from Demiov/Abrahams.

// dimovAbrahams.cpp

#include <iostream>
#include <string>

// getTypeName

template<typename T>            // (1) primary template
std::string getTypeName(T){
    return "unknown";
}

template<typename T>            // (2) primary template that overloads (1)
std::string getTypeName(T*){
    return "pointer";
}

template<>                      // (3) explicit specialization of (2)
std::string getTypeName(int*){
    return "int pointer";
}

// getTypeName2

template<typename T>            // (4) primary template
std::string getTypeName2(T){
    return "unknown";
}

template<>                      // (5) explicit specialization of (4)
std::string getTypeName2(int*){
    return "int pointer";
}

template<typename T>            // (6) primary template that overloads (4)
std::string getTypeName2(T*){
    return "pointer";
}

int main(){
    
    std::cout << std::endl;
    
    int *p;
    
    std::cout << "getTypeName(p): " << getTypeName(p) << std::endl;   
    std::cout << "getTypeName2(p): " << getTypeName2(p) << std::endl; 
    
    std::cout << std::endl;
    
}

 

Admittedly, the code looks quite boring but bear with me. I defined in line (1) the primary template getTypeName. Line 2 is an overload for pointers and line 3 a full specialisation for an int pointer. In case of getTypeName2, I made a small variation. I put the explicit specialisation (line 5) before the overload for pointers (line 6).

This reordering has surprising consequences.

dimovAbrahams

In the first case, the full specialisation for the int pointer is called and in the second case, the overload of pointers. What?  The reason for this non-intuitive behaviour is that overload resolution ignores function template specialisation. Overload resolution operates on primary templates and functions. In both cases, overload resolutions found both primary templates. In the first case (getTypeName), the pointer variant is the better fit and, therefore, the explicit specialisation for the int pointer was choosen. In the second variant (getTypeName2), also the pointer variant was choosen but the full specialisation belongs to the primary template (line 4). Consequently, it was ignored.

What's next?

While proofreading this lines I had an idea. Templates are good for more surprises. Therefore, I make a short detour from the core guidelines and I will present you a few of them. My hope is that you will remember this lines if you encounter them. 

The future of C++ speaks templates. Therefore, it's good to know more about their language.

 

 

Thanks a lot to my Patreon Supporters: Paul Baxter,  Meeting C++, Matt Braun, Avi Lachmish, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Dilettant, Marko, Ramesh Jangama, G Prvulovic, and Reiner Eiteljörge.

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 416

All 1716144

Currently are 121 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments