04 variadic templates

C++ Insights – Variadic Templates

Variadic templates are a C++ feature that looks quite magic the first time you see them. Thanks to C++ Insights, most of the magic disappears. 

 

04 variadic templates

Variadic templates are one of the powerful new constructs we have since C++11.

Variadic Templates

They are great because we can have a function that takes multiple arguments and still is strongly typed. We do not need a format specifier to cast some memory from the stack into a type. Variadic templates or in this case more precisely variadic function templates, expand into functions as we would write them. The so-called parameter pack gets expanded. During this process, each parameter is simply separated by a comma, just like we would write the function. Here is a basic example:

template<typename T>
T add(const T& arg)
{
  return arg;
}

template<typename T, typename... ARGS>
T add(const T& arg, const ARGS&... args)
{
  return arg + add(args...);
}

int main()
{
  return add(1, 2u, 3u);
}

The single argument overload is required to terminate the recursion I used here. Let’s use C++ Insights to see what’s going on under the hood:

 

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.

     

    template<typename T>
    T add(const T& arg)
    {
      return arg;
    }
    
    /* First instantiated from: insights.cpp:10 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    unsigned int add<unsigned int>(const unsigned int & arg)
    {
      return arg;
    }
    #endif
    
    
    template<typename T, typename... ARGS>
    T add(const T& arg, const ARGS&... args)
    {
      return arg + add(args...);
    }
    
    /* First instantiated from: insights.cpp:15 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    int add<int, unsigned int, unsigned int>(const int & arg, const unsigned int & __args1, const unsigned int & __args2)
    {
      return static_cast<int>(static_cast<unsigned int>(arg) + add(__args1, __args2));
    }
    #endif
    
    
    /* First instantiated from: insights.cpp:10 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    unsigned int add<unsigned int, unsigned int>(const unsigned int & arg, const unsigned int & __args1)
    {
      return arg + add(__args1);
    }
    #endif
    
    
    int main()
    {
      return add(1, 2u, 3u);
    }
    

    Pay attention to the types. I used 2u and 3u which results in two unsigned int arguments and one signed int. Due to the arrangement of the parameters the return type  add is int which leads, as C++ Insights shows the use, to an implicit cast in add. One additional insight C++ Insights shows us.

    Fold Expressions

    With C++17 and fold-expressions we can reduce our code to this:

    template<typename... ARGS>
    auto add(const ARGS&... args)
    {
      return (args + ...);
    }
    
    int main()
    {
      return add(1, 2u, 3u);
    }
    

    I really like how the new standards make us write less and less code. The result of C++ Insights changes as well:

    template<typename... ARGS>
    auto add(const ARGS&... args)
    {
      return (args + ...);
    }
    
    /* First instantiated from: insights.cpp:9 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    unsigned int add<int, unsigned int, unsigned int>(const int & __args0, const unsigned int & __args1, const unsigned int & __args2)
    {
      return static_cast<unsigned int>(__args0) + __args1 + __args2;
    }
    #endif
    
    
    int main()
    {
      return static_cast<int>(add(1, 2u, 3u));
    }
    

    Of course, you can write variadic class templates as well. Have a look at this code sample:

    template<int...>
    struct add;
    
    template<>
    struct add<>
    {
      static constexpr int value = 0;
    };
    
    template<int i, int... tail>
    struct add<i, tail...>
    {
      static constexpr int value = i + add<tail...>::value;
    };
    
    static_assert(6 == add<1, 2, 3>::value, "Expect 6");
    

    We have a variadic class template that calculates the sum of an arbitrary amount of numbers. C++ Insights shows all the instantiations that happen in the background to calculate the result. Here we can see how it pops one number each time until there are no more left:

    template<int...>
    struct add;
    
    /* First instantiated from: insights.cpp:16 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    struct add<1, 2, 3>
    {
      inline static constexpr const int value = 1 + add<2, 3>::value;
      
    };
    #endif
    
    
    /* First instantiated from: insights.cpp:13 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    struct add<2, 3>
    {
      inline static constexpr const int value = 2 + add<3>::value;
      
    };
    #endif
    
    
    /* First instantiated from: insights.cpp:13 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    struct add<3>
    {
      inline static constexpr const int value = 3 + add<>::value;
      
    };
    #endif
    
    
    template<>
    struct add<>
    {
      static constexpr int value = 0;
    };
    
    template<int i, int... tail>
    struct add<i, tail...>
    {
      static constexpr int value = i + add<tail...>::value;
    };
    
    /* PASSED: static_assert(6 == add<1, 2, 3>::value, "Expect 6"); */
    

    Reference to an Array

    One more thing about templates is that they can take a reference to an array which prevents an array from decaying to a pointer:

    template<typename T, int N>
    void Rx(T (&data)[N])
    {
      // assuming char here
      static_assert(sizeof(data) == 5);
    }
    
    int main()
    {
        char buffer[5]{};
    
        Rx(buffer);
    }
    

    In C++ Insights you can see that the instantiation contains the type as well as the size of the array:

    template<typename T, int N>
    void Rx(T (&data)[N])
    {
      // assuming char here
      static_assert(sizeof(data) == 5);
    }
    
    /* First instantiated from: insights.cpp:12 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void Rx<char, 5>(char (&data)[5])
    {
      /* PASSED: static_assert(sizeof(data) == 5); */
    }
    #endif
    
    
    int main()
    {
      char buffer[5] = {'\0', '\0', '\0', '\0', '\0'};
      Rx(buffer);
    }
    

    Another thing, aside from templates, you can see in this example the effect of braced initialization of buffer. The compiler fills all elements of the array for us with the default value. This means that we can say goodbye to the good old memset and make our programs faster and safer.

    I hope I could show you how C++ Insights can be helpful if it comes to templates. For me, it is a vital instrument when teaching and explaining templates, especially variadic templates. Try it out and tell me about your experience.

    I’d like to thank Rainer for the opportunity to share information about C++ Insights on his popular blog!

    Have fun with C++ Insights. You can support the project by becoming a Patreon or of course with code contributions.

    Stay tuned for more insights about C++ Insight … . The next post is about lambdas.

    Andreas

     

     

     

     

    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 *