C++ is Lazy: CRTP

Contents[Show]

In my previous post, Recursion, List Manipulation, and Lazy Evaluation, I wrote about the characteristics of functional programming:  The story about lazy evaluation in C++ is short. Sorry to say, but I have forgotten templates. The two advanced techniques, CRTP and expression templates, are based on lazy evaluation.

 

CRTP

But what does CRTP mean? The acronym CRTP stands for the C++ idiom Curiously Recurring Template Pattern and means a technique in C++ in which a class Derived derives from a class template Base. The key is that Base has Derived as a template argument.

template<class T>
class Base{
...
};

class Derived : public Base<Derived>{
...
};

 

If that is not mind-blowing and how does lazy evaluation kick in? At first lazy evaluation.

 

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.

As lazy as possible

The key observation for understanding the CRTP idiom is that the instantiation of a method of a class template happens only when needed. Proof?

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// lazy.cpp

#include <iostream>

template<class T> 
struct Lazy{
    void func() { std::cout << "func" << std::endl;}
    void func2(); // not defined
};

int main(){
  
  std::cout << std::endl;
    
  Lazy<int> lazy;
  lazy.func();
  
  std::cout << std::endl;
    
}

 

Although the method func2 (line 8) of the class,  Lazy is only declared but not defined; the compiler accepts the program. Because I don't call func2, I need no definition.

lazy

That is precisely the property that the CRTP uses because the definition of a method of class templates is only needed if called. The method's declaration is sufficient for the instantiation of the base class. Therefore, you can implement static polymorphism.

Static Polymorphism

Static polymorphism is quite similar to dynamic polymorphism. But contrary to dynamic polymorphism with virtual methods, the dispatch of the method calls will occur at compile time. Now, we are at the center of the CRTP idiom.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// crtp.cpp

#include <iostream>

template <typename Derived>
struct Base{
  void interface(){
    static_cast<Derived*>(this)->implementation();
  }
  void implementation(){
    std::cout << "Implementation Base" << std::endl;
  }
};

struct Derived1: Base<Derived1>{
  void implementation(){
    std::cout << "Implementation Derived1" << std::endl;
  }
};

struct Derived2: Base<Derived2>{
  void implementation(){
    std::cout << "Implementation Derived2" << std::endl;
  }
};

struct Derived3: Base<Derived3>{};

template <typename T>
void execute(T& base){
    base.interface();
}


int main(){
  
  std::cout << std::endl;
  
  Derived1 d1;
  execute(d1);
    
  Derived2 d2;
  execute(d2);
  
  Derived3 d3;
  execute(d3);
  
  std::cout << std::endl;
  
}

 

I use static polymorphism in the function template execution (lines 29 - 32). I invoke on each argument base the method base.interface. The method Base::interface in lines 7 - 9 is the critical point of the CRTP idiom. The methods dispatch to the implementation of the derived class: static_cast<Derived*>(this)->implementation().  That is possible because the method will be instantiated when called. At this point in time, the derived classes Derived1, Derived2, and Derived3 are fully defined. Therefore, the method Base::interface can use the details of its derived classes. Fascinating is the method Base::implementation (lines 10 - 12). This method plays the role of a default implementation for the static polymorphism for the class  Derived3 (line 27).

Here is the output of the program.

crtp

 

Admittedly, the only purpose of the example was to present the mechanic behind the static polymorphism. A convincing example is still missing. Here we are.

Mixins with CRTP

Mixins are a popular concept in mixing classes in new code. Therefore, it's an often-used technique in Python to change the behavior of a class by using multiple inheritances. Contrary to C++, it is legal in Python to have more than one method definition in a class hierarchy. Python uses that method that is first in the Method Resolution Order (MRO).

You can implement mixins in C++ by using CRTP. A prominent example is the class std::enable_shared_from_this. Using this class, you can create objects that return a  std::shared_ptr to themselves. You have to derive your class MySharedClass public from std::enable_shared_from_this. Now, your class MySharedClass has a method shared_from_this for creating std::shared_ptr to its objects. You can read the details about std::enable_shared_from_this in my post Specialities of std::shared_ptr.

An additional typical use-case for mixins is a class that you want to extend with the capability that their instances support the comparison of equality and inequality.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// crtpEquality.cpp

#include <iostream>
#include <string>

template<class Derived>
class Equality{};

template <class Derived>
bool operator == (Equality<Derived> const& op1, Equality<Derived> const & op2){
  Derived const& d1 = static_cast<Derived const&>(op1);     
  Derived const& d2 = static_cast<Derived const&>(op2); 
  return !(d1 < d2) && !(d2 < d1);
}

template <class Derived>
bool operator != (Equality<Derived> const& op1, Equality<Derived> const & op2){
  Derived const& d1 = static_cast<Derived const&>(op1);     
  Derived const& d2 = static_cast<Derived const&>(op2); 
  return !(op1 == op2);
}

struct Apple:public Equality<Apple>{
  Apple(int s): size{s}{};
  int size;
};

bool operator < (Apple const& a1, Apple const& a2){
  return a1.size < a2.size;
}

struct Man:public Equality<Man>{
  Man(std::string n): name{n}{}
  std::string name;
};

bool operator < (Man const& m1, Man const& m2){
  return m1.name < m2.name;
}


int main(){
  
  std::cout << std::boolalpha << std::endl;
  
  Apple apple1{5};
  Apple apple2{10}; 
  std::cout << "apple1 == apple2: " << (apple1 == apple2) << std::endl;
    
  Man man1{"grimm"};
  Man man2{"jaud"};
  std::cout << "man1 != man2: " << (man1 != man2) << std::endl;
  
  std::cout << std::endl;
    
}

 

I have implemented for the classes Apple and Man the smaller operator (lines 28 and 37). For further reasoning, I will only use the class Man for simplicity.  The class Man is publicly derived (lines 32 - 35) from Equality<Man>. I have implemented for classes of the kind Equality<Derived> the equality (lines 9 - 14) and the inequality operator (lines 16 - 21). The inequality operator uses the equality operator (line 20). The equality operator uses the fact that the smaller operator is implemented for Derived (line 13). The equality operator and inequality operator convert its operands: Derived const&: Derived const& d1 = static_cast<Derived const&>(op1).

Now, I can compare Apple and Man for equality and inequality.

 

 crtpEquality

 What's next?

 

In addition to CRTP, expression templates are also based on lazy evaluation. Expression templates are "structures representing a computation at compile time, which are evaluated only as needed to produce efficient code for the entire computation" (https://en.wikipedia.org/wiki/Expression_templates). As needed, that is the point of lazy evaluation; therefore, expression templates are the topic of 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

Tags: CRTP, Python

Comments   

0 #1 Lonna 2017-03-29 02:14
I like the helpful information you provide in your articles.
I will bookmark your blog and check again here frequently.
I am quite sure I will learn a lot of new stuff right here!
Good luck for the next!
Quote
0 #2 Kassie 2017-04-10 04:24
This post is invaluable. When can I find out more?
Quote
0 #3 nikonl810.net 2018-02-02 14:30
Very nice post. I just stumbled upon your blog and wished to say that I have really enjoyed browsing
your blog posts. After all I'll be subscribing to your rss feed
and I hope you write again very soon!
Quote
-6 #4 HGH 2018-09-08 19:24
return !(op1 == op2);

That doesn't look right
Quote
0 #5 Kulagin 2020-07-08 19:57
Thanks, this is exactly what I was after: polymorphism and inheritance while using templated member functions, which you can't achieve with virtual template and override in inherited because you can't use virtual with template.
Quote
0 #6 Praveen 2020-08-29 06:56
Thanks alot for providing information on c++20 features and making me to dive deep into c++ 20
Quote
0 #7 Daniel T 2021-05-18 15:36
Lines 18 and 19 are unused
Quote

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 4805

Yesterday 4550

Week 4805

Month 26479

All 12104688

Currently are 187 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments