fear

C++ Core Guidelines: Type Erasure

The rule “T.5: Combine generic and OO techniques to amplify their strengths, not their costs”  of the core guidelines to generic programming uses type erasure as an example. Type erasure? Really! Of course, it takes me two posts to explain this advanced template technique.

 

fear

First of all: What does type erasure mean?

  • Type Erasure: Type Erasure enables using various concrete types through a single generic interface.

Of course, you already often used type erasure in C++ or C. The C-ish type erasure is a void pointer; the C++-ish erasure is object orientation. Let’s start with a void pointer.

Void Pointer

Let’s have a closer look at the declaration of std::qsort:

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    void qsort(void *ptr, std::size_t count, std::size_t size, cmp);
    

     

    with:

    int cmp(const void *a, const void *b);
    

     

    The comparison function cmp should return a

    • negative integer: the first argument is less than the second
    • zero: both arguments are equal
    • positive integer: the first argument is greater than the second

    Thanks to the void pointer, std::qsort it is generally applicable but also quite error-prone.

    Maybe you want to sort a std::vector<int>, but you used a comparator for C-strings. The compiler can not catch this error because the type information was removed. You end with undefined behavior.

    In C++, we can do better:

    Object Orientation

    Here is a simple example, which serves as a starting point for further variations.

     

    // typeErasureOO.cpp
    
    #include <iostream>
    #include <string>
    #include <vector>
    
    struct BaseClass{                                       // (2)
    	virtual std::string getName() const = 0;
    };
    
    struct Bar: BaseClass{                                  // (4)
    	std::string getName() const override {
    	    return "Bar";
    	}
    };
    
    struct Foo: BaseClass{                                  // (4)
    	std::string getName() const override{
    	    return "Foo";
    	}
    };
    
    void printName(std::vector<const BaseClass*> vec){      // (3)
        for (auto v: vec) std::cout << v->getName() << std::endl;
    }
    
    
    int main(){
    	
    	std::cout << std::endl;
    	
    	Foo foo;
    	Bar bar; 
    	
    	std::vector<const BaseClass*> vec{&foo, &bar};   // (1)
    	
    	printName(vec);
    	
    	std::cout << std::endl;
    
    }
    

     

    std::vector<const Base*> (1) has a pointer to a constant BaseClasses. BaseClass is abstract Base Class, which is used in (3). Foo and Bar (4) are the concrete classes.

    The output of the program is not so thrilling.

     typeErasureOO

    To say it more formally. Foo and Bar implement the interface of the BaseClass and can, therefore, be used instead of BaseClass. This principle is called Liskov substitution principle and is type erasure in OO.

    In Object Orientated  Programming, you implement an interface. In dynamically typed languages such as Python, you are not interested in interfaces; you are interested in behavior.

    Templates

    Let me make a short detour.

    snake 312561 1280

    In Python, you care about behavior and not about formal interfaces. This idea is well-known as duck typing. To make it short, the expression goes back to the poem from James Whitcomb Rileys: Here it is:

     

    “When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”

    What does that mean? Imagine a function acceptOnlyDucks that only accepts ducks as an argument. All derived types can invoke the function in statically typed languages such as C++. In Python, all types, which behave like Duck‘s, can be used to invoke the function. To make it more concrete. If a bird behaves like  Duck it is a Duck. Python often uses a proverb to describe this behavior quite well.

    Don’t ask for permission; ask for forgiveness.

    In our Duck’s case, you invoke the function acceptsOnlyDucks with a bird and hope for the best. If something terrible happens, you catch the exception with an except clause. Often this strategy works very well and very fast in Python.

    Okay, this is the end of my detour. Maybe you wonder why I wrote about duck typing in this C++ post. The reason is quite straightforward. Thanks to templates, we have duck typing in C++. When you combine duck typing together with OO, it even becomes type-safe.

    std::function as a polymorphic function wrapper is a nice example of type erasure in C++.

    std::function

    std::function can accept everything which behaves like a function. To be more precise. This can be any callable such as a function, a function object, a function object created by std::bind, or just a lambda function.

    // callable.cpp
    
    #include <cmath>
    #include <functional>
    #include <iostream>
    #include <map>
    
    double add(double a, double b){
    	return a + b;
    }
    
    struct Sub{
    	double operator()(double a, double b){
    		return a - b;
    	}
    };
    
    double multThree(double a, double b, double c){
    	return a * b * c;
    }
    
    int main(){
        
        using namespace std::placeholders;
    
        std::cout << std::endl;
    
        std::map<const char , std::function<double(double, double)>> dispTable{  // (1)
            {'+', add },                                         // (2)
            {'-', Sub() },                                       // (3)
            {'*', std::bind(multThree, 1, _1, _2) },             // (4)
            {'/',[](double a, double b){ return a / b; }}};      // (5)
    
        std::cout << "3.5 + 4.5 = " << dispTable['+'](3.5, 4.5) << std::endl;
        std::cout << "3.5 - 4.5 = " << dispTable['-'](3.5, 4.5) << std::endl;
        std::cout << "3.5 * 4.5 = " << dispTable['*'](3.5, 4.5) << std::endl;
        std::cout << "3.5 / 4.5 = " << dispTable['/'](3.5, 4.5) << std::endl;
    
        std::cout << std::endl;
    
    }
    

     

    In this example, I use a dispatch table (1) which maps characters to callables. A callable can be a function (1), a function object (2), or a function object created by std::bind (3), or a lambda function. The key point of std::function is that it accepts all different function types and erases their types. std::function requires from its callables that it takes two double's and returns a double: std::function<double(double, double)>.

    To complete the example, here is the output.

    callable

     

    Before I write more about type erasure with templates in the next post, let me summarise the three techniques to implement type erasure.

    TypeErasureCompare

    You can implement type erasure with void pointers, object orientation, or templates. Only the implementation with templates is type-safe and doesn’t require a type hierarchy. The missing details of templates will follow.

    What’s next?

    I assume you want to know how type erasure with templates is implemented. Of course, you have to wait for 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, 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, 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, Rob North, Bhavith C Achar, Marco Parri Empoli, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

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

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    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++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *