What Does This Variadic Template Code Do

What does this variadic template code do?

The short answer is "it does it not very well".

It invokes f on each of the args..., and discards the return value. But it does so in a way that leads to unexpected behavior in a number of cases, needlessly.

The code has no ordering guarantees, and if the return value of f for a given Arg has an overloaded operator, it can have unfortunate side effects.

With some white space:

[](...){}(
(
f(std::forward<Args>(args)), 0
)...
);

We will start from the inside.

f(std::forward<Args>(args)) is an incomplete statement that can be expanded with a .... It will invoke f on one of args when expanded. Call this statement INVOKE_F.

(INVOKE_F, 0) takes the return value of f(args), applies operator, then 0. If the return value has no overrides, this discards the return value of f(args) and returns a 0. Call this INVOKE_F_0. If f returns a type with an overriden operator,(int), bad things happen here, and if that operator returns a non-POD-esque type, you can get "conditionally supported" behavior later on.

[](...){} creates a lambda that takes C-style variadics as its only argument. This isn't the same as C++11 parameter packs, or C++14 variadic lambdas. It is possibly illegal to pass non-POD-esque types to a ... function. Call this HELPER

HELPER(INVOKE_F_0...) is a parameter pack expansion. in the context of invoking HELPER's operator(), which is a legal context. The evaluation of arguments is unspecified, and due to the signature of HELPER INVOKE_F_0... probably should only contain plain old data (in C++03 parlance), or more specifically [expr.call]/p7 says: (via @T.C)

Passing a potentially-evaluated argument of class type (Clause 9) having a nontrivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.

So the problems of this code is that the order is unspecified and it relies on well behaved types or specific compiler implementation choices.

We can fix the operator, problem as follows:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) {
[](...){}((void(f(std::forward<Args>(args))), 0)...);
}

then we can guarantee order by expanding in an initializer:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) {
int unused[] = {(void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}

but the above fails when Args... is empty, so add another 0:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) {
int unused[] = {0, (void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}

and there is no good reason for the compiler to NOT eliminate unused[] from existance, while still evaluated f on args... in order.

My preferred variant is:

template <class...F>
void do_in_order(F&&... f) {
int unused[] = {0, (void(std::forward<F>(f)()), 0)...};
void(unused); // suppresses warnings
}

which takes nullary lambdas and runs them one at a time, left to right. (If the compiler can prove that order does not matter, it is free to run them out of order however).

We can then implement the above with:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) {
do_in_order( [&]{ f(std::forward<Args>(args)); }... );
}

which puts the "strange expansion" in an isolated function (do_in_order), and we can use it elsewhere. We can also write do_in_any_order that works similarly, but makes the any_order clear: however, barring extreme reasons, having code run in a predictable order in a parameter pack expansion reduces surprise and keeps headaches to a minimum.

A downside to the do_in_order technique is that not all compilers like it -- expanding a parameter pack containing statement that contains entire sub-statements is not something they expect to have to do.

Variadic templates in C++ and how they work

I think that you might miss the point that when you step through this in the debugger, you're entering a completely different function each time.

(Note that val1 is not used in the "next" call, so you're passing one argument less each time.)

If there weren't variadic templates, you would need to spell these out as separate templates, but it would be exactly the same:

template <typename Res, typename T>
void Sum(Res& result, T val)
{
result += val;
}

template <typename Res, typename T1, typename T2>
void Sum(Res& result, T1 val1, T2 val2)
{
result += val1;
Sum(result, val2);
}

template <typename Res, typename T1, typename T2, typename T3>
void Sum(Res& result, T1 val1, T2 val2, T3 val3)
{
result += val1;
Sum(result, val2, val3);
}

template <typename Res, typename T1, typename T2, typename T3, typename T4>
void Sum(Res& result, T1 val1, T2 val2, T3 val3, T4 val4)
{
result += val1;
Sum(result, val2, val3, val4);
}

template <typename Res, typename T1, typename T2, typename T3, typename T5>
void Sum(Res& result, T1 val1, T2 val2, T3 val3, T4 val4, T5 val5)
{
...

and so on, as many of them as you need.

The variadic template makes the compiler generate all of these "on demand" and saves you from writing them all out.

One way to look at it is that regular function templates (with only type parameters) generate overloads with the same number of parameters; variadic templates let you also generate overloads with varying amounts of parameters.

Direct access is impossible because you don't know how many elements there inside the parameter pack, what their types are, and they don't have names.

If you want to access a specific argument you also need to make it explicit.

Variadic template data structures

I dedicate this answer to @super that provides the link and the technical name for research. Thanks !

1 and 3. Let's suppose the template primary is:

template <size_t ...Rest>
struct Test {
int value = 1;
};

This means that the others (partial specialization) can be deducted, and with this different methods and values within the Struct / Class can be defined, example:

template <size_t A>
struct Test <A, A*2> {
int value = 2;
}

Any "Test" structure that was initialized with 2 parameters, and the second is the double of first (A*2) have value = 2. Example:

Test<5, 10> -> value = 2
Test <3, 6> -> value = 2;

Anything other than: "<A, A * 2>" will be assigned to the primary template

Test <1, 2, 3, 4> -> value = 1;
Test <4, 5> -> value = 1;
Test <3, 6, 8> -> value = 1;

That said, replying to the first topic:

template <class ...Rest>
struct Test {
int value = 1;
};

template <class T, ...Rest>
struct Test <T, Rest...> {
int value = 2;
};

The condition of the secondary template must satisfy the primary, which in this case is: "template <class ... Rest> struct Test {}", then any secondary template: template <class T, ... Rest> struct Test <T , Rest ...> will always be true, because Rest ... means 0 or more, and "T" is the first argument

Example below:

Test<int, bool> -> value = 2
Test <bool, float> -> value = 2
Test <bool> -> value = 2
Test <std::string> -> value = 2

But you can also create other secondary templates:

template <class ...Rest>
struct Test {
int value = 1;
};

template <>
struct Test <int, float> {
int value = 2;
};

template <class ...Rest>
struct Test <int, float, bool, Rest...> {
int value = 3;
};

In this example above, you are saying:

If the type is "<int, float>" the value will be 2
[Exact two arguments, int and float]

If the type is <int, float, bool, Rest ...> the value will be 3
[The first three arguments are int and float and bool, the rest if any]

Example:

Test <int, float> -> value = 2

Test <int, float, bool> -> value = 3
Test <int, float, bool, std::string> -> value = 3
Test <int, floot, bool, std::string, char> -> value = 3

Any other that does not satisfy any of the secondary templates will be assigned to the primary

Test <bool, float> -> value = 1
Test <int> -> value = 1
Test <int, float, char> -> value = 1

2.
Let's assume the following code

...
template <class T, class ...Rest>
void Print(T data, Rest ...others){
std::cout << data.first << std::endl;
}

int main(){
Test<int, float, bool> ex(15, 41.59, true);

Print(ex);
return 0;
}

Print waits as a parameter (T data, Rest ... others), that is: "data" is of type T. Then T data will be "ex"

If we change the print func to Print(Test <T, Rest ...> & data), C ++ will understand that the template to be passed must be a structure or class that satisfies the condition to be true, example: Print < int, float, bool > (ex).

"< int, float, bool >" was obtained automatically by c / c ++ of the variable "ex"

See the example below:

...
template <class A, class B, class C>
void Print(Test<A, B, C> &data, A za, B zq){
std::cout << data.first << std::endl;
std::cout << za << std::endl; //float
std::cout << zq << std::endl; //bool
}

int main(){
Test<int, float, bool> ex(15, 41.59, true);

Print(ex, 50);
return 0;
}

Again "< int, float, bool >" was obtained automatically by c / c ++ of the variable "ex" when Print(ex)

A is "int"

B is "float'

C is "bool"

The second parameter (A za) of the function "Print", must the same type (int) of the parameter that was deducted by "A" (int), the same for the third parameter (B zq)

3. Let's assume the following code:

template <class T>
struct Helper {
int value = 1;
};

template <>
struct Helper <int> {
int value = 2;
};

template <class A, class B, class C>
struct Helper<Test<A, B, C>> {
int value = 3;

void PrintA(A value){}

void PrintB(B value){}

void PrintAll(A aval, B bval, C cval){}
};

Any "Helper" structure that was initialized with a single < int > parameter will have value = 2

Any "Helper" structure that was initialized with a single parameter different from < int > and Test<A, B, C> will have value = 1;

Example:

Helper<int> -> value = 2

Helper<Test<int,bool,float>> -> value = 3
Helper<Test<int,float,char>> -> value = 3
Helper<Test<char,std::string,double>> -> value = 3

Anything other than: "<int>" and "<Test<A,B,C>>" will be assigned to the primary template

Helper <bool> -> value = 1
Helper <float> -> value = 1
Helper <char> -> value = 1

Note that the secondary templates must satisfy the primary, ie: template < class T > struct Helper {}

Therefore, the secondary templates after the definition of "Helper" must contain only a single "T", example < int >, or < float >, or <Test <A, Rest ... >>


template <class A, class B, class C>
struct Helper <Test <A, B, C >>

This means: The template to be passed to Helper must be a type of struct / class that satisfies the expression, so:

Helper <Test <int, float, bool >> satisfies the condition, but

Helper <Test <int, float >> does not satisfy, because "A" was deducted for int, "B" for float, and "C" was empty

Are variadic templates a potential code bloat?

The short answer is: the "you only pay for what you use" principle still applies exactly as before.

The longer answer can be seen by comparing the generated code for two hypothetical implementations e.g.

#include <iostream>

template <typename T>
void func1(T& v) {
v = -10;
}

template <typename T1, typename T2>
void func1(T1& v1, T2& v2) {
func1(v1); func1(v2);
}

// More unused overloads....
template <typename T1, typename T2, typename T3>
void func1(T1& v1, T2& v2, T3& v3) {
func1(v1); func1(v2); func1(v3);
}

int main() {
double d;
int i;
func1(d);
func1(i);
std::cout << "i=" << i << ", d=" << d << std::endl;
func1(d,i);
std::cout << "i=" << i << ", d=" << d << std::endl;
}

With a modern compiler this pretty much reduces to exactly what you'd have written if you wanted to avoid templates all together. In this "traditional" C++03 templated code my version of g++ inlines (in the compiler, not keyword sense) the whole lot and there's no obvious hint that the initializations are done via reference in a template function, several times, in different ways.

Compared with the equivalent variadic approach:

#include <iostream>
#include <functional>

void func1() {
// end recursion
}

template <typename T, typename ...Args>
void func1(T& v, Args&... args) {
v = -10;
func1(args...);
}

int main() {
double d;
int i;
func1(d);
func1(i);
std::cout << "i=" << i << ", d=" << d << std::endl;
func1(d,i);
std::cout << "i=" << i << ", d=" << d << std::endl;
}

This also produces almost identical code - some of the labels and mangled names are different as you'd expect, but the diff of the generated asm produced by g++ -Wall -Wextra -S (a 4.7 snapshot) has no significant differences. The compiler basically is writing all of the overloads your program requires on the fly and then optimizing as before.

The following non template code also produces almost identical output:

#include <iostream>
#include <functional>

int main() {
double d;
int i;
d= -10; i=-10;
std::cout << "i=" << i << ", d=" << d << std::endl;
d= -10; i=-10;
std::cout << "i=" << i << ", d=" << d << std::endl;
}

Here again the only noticeable differences are the labels and symbol names.

The point is a modern compiler can do "what's right" without much hassle in template code. If what you're expressing is simple underneath all the template mechanics the output will be simple. If it's not then the output will be more substantial, but so would the output be if you'd avoided templates entirely.

Where this gets interesting (in my view) however is this: all of my statements were qualified with something like "with an decent modern compiler". If you're writing variadic templates you can almost be certain that what you're using to compile is a decent modern compiler. No clunky old relic compilers support variadic templates.

How do I do variadic templates of variadic arguments

The fact that ColumnValue is a template doesn't make any difference for the signature of print. We can just take a regular parameter pack and let the compiler figure out the different types.

Secondly we can't loop over a parameter pack. We can however use a fold-expression.

The end result would look something like this

template <typename... T>
void print(std::string firstArg, const T& ...args) {
(std::cout << ... << args.value) << std::endl;
}

If you want to insert a newline between each argument, you would need some kind of helper for that. The simplest idea would be.

template <typename T>
void print_helper(const T& arg) {
std::cout << arg << '\n';
}

template <typename... T>
void print(std::string firstArg, const T& ...args) {

(print_helper(args.value), ...);
}

C++ variadic template empty argument specialization

In C++17, you don't need to split parameter packs into head and tail in most cases. Thanks to fold expressions, many operations on packs become much easier.

// Some generic predicate.
template <typename T>
bool condition_hold(T) {
return true;
}

// Make this whatever you want.
void do_something_with(int);

template<typename... Ts>
auto func(int val, Ts... args) {
// Fold expression checks whether the condition is true for all
// elements of the parameter pack.
// Will be true if the parameter pack is empty.
if ((condition_hold(args) && ...))
do_something_with(val);
}

int main() {
// Ts type parameters are deduced to <float, float>.
func(100, 1.f, 2.f);
return 0;
}

To check whether the pack was empty and handle this case specially, you can do:

template<typename... Ts>
auto func(int val, Ts... args) {
if constexpr (sizeof...(Ts) == 0) {
// handle empty pack
}
else {
// handle non-empty pack
}
}

Your specialization couldn't have worked because func<> needs to take at least one parameter. A specialization such as

template<typename T>
void func<T>(int val);

Wouldn't be valid either, because it wold be a partial specialization which is only allowed for classes.
However, if the base template only takes a pack, we can fully specialize it:

template<typename... Ts>
void func(int val, Ts... args);

template<>
void func<>(int val);

How can I make this variadic template code shorter using features from C++14 and C++1z?

#include <type_traits>

template <typename F, typename... Ts>
constexpr bool is_one_of = (std::is_same<F, Ts>{} || ...);

template <typename...>
constexpr bool is_unique = true;

template <typename F, typename... Ts>
constexpr bool is_unique<F, Ts...> = is_unique<Ts...> && !is_one_of<F, Ts...>;

DEMO



Related Topics



Leave a reply



Submit