Function Templates - More Details about Explicit Template Arguments and Concepts

Contents[Show]

In the last post "Function Templates", I wrote about the overloading of function templates and automatically deducing the return type of a function template. Today, I dive deeper and specify explicitly the template arguments of a function template and bring concepts into the play.

 templatesNew

Before I start this post, I have to make two general remarks. Today, I write about a don't and a do.

  • Don't: In general, you should not explicitly specify the template arguments for function templates.
  • Do: In general, you should use restricted template parameters (concepts).

Let me start with the don't.

Explicitly Specifying the Template Arguments

You can explicitly specify the template arguments. This is necessary if the compiler cannot deduce the type parameters of the function templates, or you use class template. With C++17, the compiler can automatically deduce the type of the template arguments from the constructor arguments:

std::vector<int> myVec{1, 2, 3, 4, 5};  // (1)
std::vector myVec{1, 2, 3, 4, 5};       // (2)

Instead of the line (1), you can just use line (2) in C++17. I will write more about this feature in an upcoming post.

Once more. In general, you should not specify the template arguments. But I intentionally did it.

// maxExplicitTypeParameter.cpp

template <typename T>
T max(const T& lhs,const T& rhs) {
    return (lhs > rhs)? lhs : rhs;
}

int main() {
  
  auto res1 = max<float>(5.5, 6.0);
  auto res2 = max<bool>(5.5, 6.0);
  auto res3 = max(5.5, 6.0);
  
}

 What is happening in lines (1) - (3)? C++ Insights helps me to analyze the code. These are the crucial output lines:

maxExplicitInstantiationCppInsightsNew

 

  • The call max<float>(5.5, 6.0) in line (1) causes the instantiation of the function template max for double (line 10). Consequently, both doubles are converted to const float (line 40). 
  • The call max<bool>(5.5,  6.0) in line (2) puts a lot of work on the compiler's shoulder. 
    1. The invocation causes the compiler to implicitly convert the doubles to bool.
    2. To compare both bools inside the function body (line 23), they have to be promoted to int (line 23).
    3. Finally, the return type res2 is bool. Consequently, the int must be converted to bool.
  • The call max(5.5, 6.0)  in line (3) does exactly the job you want. Not conversion or promotion is necessary.

Honestly, I would consider the max<bool>(5.5, 6.0) as an error and not intentionally. But this happens, when you want to be smarter than the compiler.

There is a related syntax to the explicit specification of template arguments that you may sometimes see but puzzles you: max<>(5.5, 6.0); When I ask in my seminars: What could that mean? Based on the previous theory, half of my participants would guess it right

Imagine, you have a function and a function template max:

// maxCompilerDeduction.cpp

double max(const double& lhs, const double& rhs) {
  return (lhs > rhs)? lhs : rhs;
}

template <typename T>
T max(const T& lhs,const T& rhs) {
    return (lhs > rhs)? lhs : rhs;
}

int main() {
  
  auto res1 = max(5.5, 6.0);    // (1)
  auto res2 = max<>(5.5, 6.0);  // (2)
  
}

As we learned in the previous post "Function Templates", the compiler prefers the function when the function and the function templates are ideal fits. Okay, this answers line (1). Line (2) expresses, that the compiler should only consider the function template max and ignore the function max. Additionally, the compiler automatically deduces the template parameters for the function arguments. Consequently, C++ Insights shows, that the compiler instantiated max for double.

 maxCompilerDeductionCppInsights

So far, I only considered function overloading with functions and function templates having unrestricted type parameters. Okay, I can do better and should. Now, I bring restricted type parameters (concepts) into the play. This means, here is my do for this post: Use restricted type parameters if possible.

Overloading with Concepts

C++20 has the concept std::totally_ordered. A type T supports a total order, if it supports partial order and any elements of T can be compared. Let me be more formal:

A type T supports partial order, if the following relations for all elements a, b, and c of the type T hold:

  1. a <= a (reflexive)
  2. If a <= b and b <= c then a <= c (transitive)
  3. If a <=b and b <= a then a == b (antisymmetric)

A type T supports total order, if it supports partial order and all elements of T can be compared.

  1. a <= b or b <= b (comparable)

The following program uses the concept std::totally_ordered:

// maxUnconstrainedConstrained.cpp

#include <iostream>
#include <concepts>

class Account {
 public:
    explicit Account(double b): balance(b) {}
    double getBalance() const { 
        return balance;
    }
 private:
    double balance;
};
  
Account max(const Account& lhs, const Account& rhs) {  // (1)
    std::cout << "max function\n";
    return (lhs.getBalance() > rhs.getBalance())? lhs : rhs;
}

template <std::totally_ordered T>                       // (2)
T max(const T& lhs,const T& rhs) {                     
    std::cout << "max restricted function template\n";
    return (lhs > rhs)? lhs : rhs;
}

template <typename T>                                   // (3)
T max(const T& lhs,const T& rhs) {                   
    std::cout << "max unrestriced function template\n";
    return (lhs > rhs)? lhs : rhs;
}


int main() {
  
    Account account1(50.5);
    Account account2(60.5);
    Account maxAccount = max(account1, account2);       // (4)
  
    int i1{50};
    int i2(60);
    int maxI = max(i2, i2);                             // (5)
  
}

The program defines a function max taking two Accounts (line 1), and two function templates. The first function template max in line (2) requires that the values support a total ordering. The second function template max has no type constraints on its type parameters. As you might expect, the compiler chooses the best fitting overload. A function template a fits better than another function template b, if a is more specialized than b. This means that it chooses the function for Accounts (line 4) and the function template max with restricted type parameters for int (line 5).

The comments in the various max function make the decisions of the compiler transparent.

maxCompilerExplorer

You can study the program on the Compiler Explorer.

What's next?

After the basics to function templates, I present in my next post the basics about class templates. Additionally, I will write in this context about generic member functions, inheritance with templates, and alias templates.

 

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, and Andi Ireland.

 

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

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online-seminars or face-to-face seminars world-wide. 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 Patrick 2021-06-03 09:26
Thank you for the nice post, I really enjoyed reading it.

I am not sure, however, if your explanation of the max instantiation is correct.
I will repeat it here:
The call max(5.5, 6.0) in line (2) puts a lot of work on the compiler's shoulder.

1. The invocation causes the compiler to implicitly convert the doubles to bool.
2. To compare both bools inside the function body (line 23), they have to be promoted to int (line 23).
3. Finally, the return type res2 is bool. Consequently, the int must be converted to bool.

In my opinion the third step will not happen, since lhs and rhs are already references to bool. The arguments are converted from double to bool when the function is invoked in step 1. The integer promotion in step 2 does not change the types of the function parameters, so both lhs and rhs are still of type bool and therefore do not have to be converted.
Quote
0 #2 Rainer Grimm 2021-06-16 07:43
Quoting Patrick:


In my opinion the third step will not happen, since lhs and rhs are already references to bool. The arguments are converted from double to bool when the function is invoked in step 1. The integer promotion in step 2 does not change the types of the function parameters, so both lhs and rhs are still of type bool and therefore do not have to be converted.

Thanks for your comment. Let me think about it.
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

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 944

Yesterday 7436

Week 30649

Month 170856

All 6610937

Currently are 153 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments