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.
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:
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:
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
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, 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,and Matt Godbolt.
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)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!