TemplateMetaprogramming

Template Metaprogramming – How it Works

In my last post, “Template Metaprogramming – How it All Started“, I wrote about the roots of template metaprogramming. I presented the hello world of template metaprogramming: calculating the factorial of a number at compile time. In this post, I will write about how template metaprogramming can modify types at compile time.

TemplateMetaprogramming The factorial program in the last post, “Template Metaprogramming – How it All Started” was an excellent example but not idiomatic for template metaprogramming. Manipulating types at compile time is typical in template metaprogramming.

Type Manipulation at Compile Time

For example, here is what std::move is conceptionally doing:

static_cast<std::remove_reference<decltype(arg)>::type&&>(arg);

std::move takes its argument arg, deduces its type (decltype(arg)), removes its reference (std::remove_reverence), and casts it to an rvalue reference (static_cast<...>::type&&>). Essentially,
std::move is an rvalue reference cast. Now, move semantics can kick in.

How can a function remove constness from its argument?

// removeConst.cpp

#include <iostream>
#include <type_traits>

template<typename T >
    struct removeConst {
    using type = T;        // (1)
};

template<typename T >
    struct removeConst<const T> {
    using type = T;       // (2)
};

int main() {

    std::cout << std::boolalpha;
    std::cout << std::is_same<int, removeConst<int>::type>::value << '\n';       // true    
    std::cout << std::is_same<int, removeConst<const int>::type>::value << '\n'; // true

}

I implemented removeConst the way std::remove_const is probably implemented in the type-traits library. std::is_same from the type-traits library helps me to decide at compile-time if both types are the same. In case of removeConst<int> the primary or general class template kicks in; in case of removeConst<const int>, the partial specialization for const T applies. The critical observation is that both class templates return the underlying type in (1) and (2) via the alias type. As promised, the constness of the argument is removed.

There are additional observations:

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    • Template specialization (partial or full) is conditional execution at compile-time. Let me be more specific: When I use removeConst a non-constant int, the compiler chooses the primary or general template. When I use a constant int, the compiler chooses the partial specialization for const T.
    • The expression using type = T serves as the return value, which is, in this case, a type.
    • When you study the program removeConst.cpp on C++ Insights, you see that the expression that the expression std::is_same<int, removeConst<int>::type>::value boils down to the boolean value std::integral_constant<bool, true>::value that is displayed as true.

    Let me step back and write about template metaprogramming for a more conceptual view.

    More Meta

    At run time, we use data and functions. At compile time, we use metadata and metafunctions. Quite logically, it’s called meta because we do metaprogramming.

    Metadata

    Metadata are values that metafunctions us at compile time.

    There are three types of values:

    • Types such as int, or double
    • Non-types such as integrals, enumerators, pointers, references, and floating-points with C++20
    • Templates such as std::vector, or std::deque

    You can read more about the three types of values in my previous post, “Alias Templates and Template Parameters“.

    Metafunctions

    Metafunctions are functions that are executed at compile time.

    Admittedly, this sounds strange: Types are used in template metaprogramming to simulate functions. Based on the definition of metafunctions, constexpr functions that can be executed at compile time, are also metafunctions. The same holds for consteval functions in C++20.

    Here are two metafunctions.

    template <int a , int b>
    struct Product {
        static int const value = a * b;
    };
    
    template<typename T >
    struct removeConst<const T> {
        using type = T;
    };
    

    The first metafunction Product returns a value, and the second one removeConst returns a type. The name value and type are just naming conventions for the return values. If a meta-function returns a value, it is called a value; if it returns a type, it is called a type. The type-traits library follows exactly this naming convention.

    It is quite enlightening to compare functions with metafunctions.

    Functions versus Metafunctions

    The following function power and the metafunction Power calculate pow(2, 10) at run time and compile time.

    // power.cpp
    
    #include <iostream>
    
    int power(int m, int n) {                               
        int r = 1;
        for(int k = 1; k <= n; ++k) r *= m;
        return r;                                        
    }
    
    template<int m, int n>                              
    struct Power {
        static int const value = m * Power<m, n-1>::value;
    };
                              
    template<int m>                                     
    struct Power<m, 0> {                                   
        static int const value = 1;                       
    };
    
    int main() {
    	
        std::cout << '\n';	
    	
        std::cout << "power(2, 10)= " << power(2, 10) << '\n';
        std::cout << "Power<2,10>::value= " << Power<2, 10>::value << '\n';
    	
        std::cout << '\n';
    }
    

    This is the main difference:

    • Arguments: The function arguments go into the round brackets (( … )), and the metafunction arguments go into the sharp brackets (< ...>). This observation also holds for defining the function and the meta function. The function uses round brackets and metafunction sharp brackets. Each metafunction argument produces a new type.
    • Return value: The function uses a return statement, and the meta function is a static integral constant value.

    I elaborate more on this comparison in the upcoming post about constexpr and consteval functions. Here is the output of the program.

    power

    power is executed at run time and Power at compile time, but what is happening in the following example?

    // powerHybrid.cpp
    
    #include <iostream>
    
    template<int n>
    int Power(int m){
        return m * Power<n-1>(m);
    }
    
    template<>
    int Power<0>(int m){
        return 1;
    }
    
    int main() {
        
        std::cout << '\n';
    
        std::cout << "Power<0>(10): " << Power<0>(20) << '\n';
        std::cout << "Power<1>(10): " << Power<1>(10) << '\n';
        std::cout << "Power<2>(10): " << Power<2>(10) << '\n';
        
    
        std::cout << '\n';
    
    }
    

    The question is obvious: Is Power a function or a metafunction? I promise the answer to this question gives you more insight.

    What’s next?

    In my next post, I will analyze the function/metafunction Power and introduce the type-traits library. The type traits library is idiomatic for compile-time programming in C++.

    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, 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, 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, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    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

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    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 *