Using Recursion in Variadic Template Functions

Although recursion dooms show_list1() aspirations to be a useful function, properly used recursion provides a solution to accessing pack items. The central idea is to unpack the function parameter pack, process the first item in the list, then pass the rest of the list on to a recursive call, and so on, until the list is empty. As usual with recursion, it’s important to make sure that there is a call that terminates the recursion. Part of the trick involves changing the template heading to this:

template<typename T, typename... Args>
void show_list3( T value, Args... args)

With this definition, the first argument to show_list3() gets picked up as type T and is assigned to value. The remaining arguments are picked up by Args and args. This allows the function to do something with value, such as display it. Then the remaining arguments, in the form args..., can be passed to a recursive call of show_list3(). Each recursive call then prints a value and passes on a shortened list until the list is exhausted.

Listing 18.9 presents an implementation that, although not perfect, illustrates the technique.

Listing 18.9. variadic1.cpp


//variadic1.cpp -- using recursion to unpack a parameter pack
#include <iostream>
#include <string>
// definition for 0 parameters -- terminating call
void show_list3() {}

// definition for 1 or more parameters
template<typename T, typename... Args>
void show_list3( T value, Args... args)
{
    std::cout << value << ", ";
    show_list3(args...);
}

int main()
{
    int n = 14;
    double x = 2.71828;
    std::string mr = "Mr. String objects!";
    show_list3(n, x);
    show_list3(x*x, '!', 7, mr);
    return 0;
}


Program Notes

Consider this function call:

show_list3(x*x, '!', 7, mr);

The first argument matches T to double and value to x*x. The remaining three types (char, int, and std::string) are placed in the Args pack, and the remaining three values ('!', 7, and mr) are placed in the args pack.

Next, the show_list3() function uses cout to display value (approximately 7.38905) and the string ", ". That takes care of displaying the first item in the list.

Next comes this call:

show_list3(args...);

This, given the expansion of args..., is the same as the following:

show_list3('!', 7, mr);

As promised, the list is shortened by one item. This time T and value become char and '!', and the remaining two types and values are packed into Args and args, respectively. The next recursive call processes these reduced packs. Finally, when args is empty, the version of show_list3() with no arguments is called, and the process terminates.

Here is the output for the two function calls in Listing 18.5:

14, 2.71828, 7.38905, !, 7, Mr. String objects!,

Improvements

We can improve show_list3() with a couple of changes. As it stands, the function displays a comma after every item in the list, but it would be better to omit the comma after the last item. This can be accomplished by adding a template for just one item and having it behave slightly differently from the general template:

// definition for 1 parameter
template<typename T>
void show_list3(T value)
{
    std::cout << value << ' ';
}

Thus, when the args pack is reduced to one item, this version is called, and it prints a newline instead of a comma. Because it lacks a recursive call to show_list3(), it also terminates the recursion.

The second area for improvement is that the current version passes everything by value. This is okay for the simple types we’ve used, but it’s inefficient for classes of large size that might be printable by cout. It would be better to use const references. With variadic templates, you can impose a pattern on the unpacking. Instead of using

show_list3(Args... args);

you can use this:

show_list3(const Args&... args);

That will cause each function parameter to have the const& pattern applied. Thus, instead of std::string mr, the final paring of parameters becomes const std::string& mr.

Listing 18.10 incorporates these two changes.

Listing 18.10. variadic2.cpp


// variadic2.cpp
#include <iostream>
#include <string>

// definition for 0 parameters
void show_list() {}

// definition for 1 parameter
template<typename T>
void show_list(const T& value)
{
    std::cout << value << ' ';
}

// definition for 2 or more parameters
template<typename T, typename... Args>
void show_list(const T& value, const Args&... args)
{
    std::cout << value << ", ";
    show_list(args...);
}

int main()
{
    int n = 14;
    double x = 2.71828;
    std::string mr = "Mr. String objects!";
    show_list(n, x);
    show_list(x*x, '!', 7, mr);
    return 0;
}


Here is the output:

14, 2.71828
7.38905, !, 7, Mr. String objects!

More C++11 Features

C++11 adds many more features than this book can cover, even ignoring the fact that many of them are not widely implemented at the time this book was written. Still, it’s worth taking a quick look at the nature of some of these features.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.157.142