super charged engine 2770374 1280

C++ Core Guidelines: Template Definitions

Template definitions deal with guidelines that are specific to a template implementation. This means, in particular, these rules focus on how a template definition depends on its context.

 super charged engine 2770374 1280

 Here are today’s rules for template definitions:

The first rule is quite special.

T.60: Minimize a template’s context dependencies

Honestly, it took me a few moments to get this rule. Let’s look at the function templates sort and algo. Here is a simplified example of the guidelines.

template<typename C>
void sort(C& c)
{
    std::sort(begin(c), end(c)); // necessary and useful dependency
}

template<typename Iter>
Iter algo(Iter first, Iter last) {
    for (; first != last; ++first) {
        auto x = sqrt(*first); // potentially surprising dependency: which sqrt()?
        helper(first, x);      // potentially surprising dependency:
                               // helper is chosen based on first and x    
}

 

 

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.

     

    It would be optimal but not always manageable if a template operates only on its arguments. This holds for the function template sort but not for algo.The function template algo has dependencies to sqrt and the function helper. Ultimately, the implementation of algo introduces more dependencies than the interface shows.

    T.61: Do not over-parameterize members (SCARY)

    If a template member does not depend on a template parameter, it removes from the template. A member may be a type or a method. By following this rule, you may decrease the code size because the non-generic code is factored out.

    The example from the guidelines is relatively straightforward.

    template<typename T, typename A = std::allocator{}>
        // requires Regular<T> && Allocator<A>
    class List {
    public:
        struct Link {   // does not depend on A
            T elem;
            T* pre;
            T* suc;
        };
    
        using iterator = Link*;
    
        iterator first() const { return head; }
    
        // ...
    private:
        Link* head;
    };
    
    List<int> lst1;
    List<int, My_allocator> lst2;
    

     

    The type Link does not depend on the template parameter A. So I can remove it and use it in List2.

    template<typename T>
    struct Link {
        T elem;
        T* pre;
        T* suc;
    };
    
    template<typename T, typename A = std::allocator{}>
        // requires Regular<T> && Allocator<A>
    class List2 {
    public:
        using iterator = Link<T>*;
    
        iterator first() const { return head; }
    
        // ...
    private:
        Link* head;
    };
    
    List<int> lst1;
    List<int, My_allocator> lst2;
    

     

    Was this an easy job? Yes? No! The title of this rule is about SCARY. What does that mean? I’m curious, but honestly, you can ignore the following lines.

    The acronym SCARY describes assignments and initializations that are Seemingly erroneous (appearing Constrained by conflicting generic parameters) but Actually work with the Right implementation (unconstrained bY the conflict due to minimized dependencies).

    I hope you get it. For the details, see N2911. In order not to bore you, here is the apparent idea applied to the standard container iterator: they have no dependency on the containers, key_compare, hasher, key_equal, or allocator types.

    The next rule helps to reduce code bloat.

    T.62: Place non-dependent class template members in a non-templated base class

    Let me say it more informally: put the template’s functionality which does not depend on the template parameters, in a non-templated base class.

    The guidelines present a quite obvious example.

    template<typename T>
    class Foo {
    public:
        enum { v1, v2 };
        // ...
    };
    

     

    The enumeration is independent of the type parameter T and should, therefore, be placed in a non-templated base class.

    struct Foo_base {
        enum { v1, v2 };
        // ...
    };
    
    template<typename T>
    class Foo : public Foo_base {
    public:
        // ...
    };
    

     

    Now, Foo_base can be used without template arguments and template instantiation.

    This technique is quite interesting if you want to reduce your code size. Here is a simple class template Array.

     

    // genericArray.cpp
    
    #include <cstddef>
    #include <iostream>
    
    template <typename T, std::size_t N>
    class Array{
    public:
        Array()= default;
        std::size_t getSize() const{
            return N;
        }
    private:
      T elem[N];
    };
    
    int main(){
    
        Array<int, 100> arr1;
        std::cout << "arr1.getSize(): " << arr1.getSize() << std::endl;
    
        Array<int, 200> arr2;
        std::cout << "arr2.getSize(): " << arr2.getSize() << std::endl;
        
    }
    

     

    If you study the class template Array, you will see that the method getSize is the same except for the type parameter N. Let me refactor the code and declare a class template Array that depends on the type parameter T.

     

    // genericArrayInheritance.cpp
    
    #include <cstddef>
    #include <iostream>
    
    
    template<typename T>
    class ArrayBase {
    protected:
        ArrayBase(std::size_t n): size(n) {} 
        std::size_t getSize() const {
            return size;
        };
    private:
        std::size_t size;
    };
    
    template<typename T, std::size_t n>
    class Array: private ArrayBase<T>{
    public:    
        Array(): ArrayBase<T>(n){}
        std::size_t getSize() const {
            return  ArrayBase<T>::getSize();
        }
    private:
        T data[n]; 
    };   
    
    
    int main(){
    
        Array<int, 100> arr1;
        std::cout << "arr1.getSize(): " << arr1.getSize() << std::endl;
    
        Array<int, 200> arr2;
        std::cout << "arr2.getSize(): " << arr2.getSize() << std::endl;
        
    }
    

     

    Array has two template parameters for the type T and the size n, but ArrayBase only has one template parameter for the type T. Array derives from ArrayBase. This means ArrayBase is shared between all instantiations of Array, which uses the same type T. In the concrete case, the getSize method of Array uses the getSize method of ArrayBase.

    Thanks to CppInsight, I can show you the compiler-generated code.

    Here is the instantiation of ArrayBase<int>:

    ArrayBase

    And here the instantiation for Array<int, 100> and Array<int, 200>:

    Array

    Array200

     

     

     

     

     

     

     

     

     

     

    What’s next?

    Of course, there are more rules left to template definitions. So my story continues with the next post. I hope my explanation of template definitions is good enough because many programmers fear templates. To me, the ideas of templates are easy to get, but the syntax still has potential.

     

     

     

     

     

    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 *