Click here to Skip to main content
15,881,600 members
Articles / Programming Languages / C++17

C++ 17 variadic template pack to runtime

Rate me:
Please Sign up or sign in to vote.
4.68/5 (8 votes)
7 Jan 2018CPOL2 min read 19.7K   9   6
Reduce function recursion with variadic templates

Introduction

In my previous C++ 17 article here I didn't like much std::any. However I 've thought of an interesting trick that can reduce the recursion needed in variadic template functions.

C++ 11 and C++ 17

First, the old way.  From Cake Processor good article about variadic printf we have this:

 

C++
void safe_printf(const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        std::cout << *s++;
    }
}

template<typename T, typename... Args>
void safe_printf(const char *s, T value, Args... args)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::cout << value;
                safe_printf(s + 1, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        std::cout << *s++;
    }
    throw std::logic_error("extra arguments provided to printf");
}

 

Two functions. The variadic template one and the final const char* one, that is called last when all the parameters in the pack have expanded. The most important  difficuly is the compile-level recursion that must occur in all such templates.

However, remember that the parameter pack expands parameters with comma separator:

template <typename ... Args> 
void foo(Args ... args)
{
    
}

foo(1,2,3,"hello"); // means that args is unpacked as such.

 

 

Why not use the initializer_list to store these values in a vector, so they can be accessed in run time using a for loop?

template <typename ... Args>
void foo(Args ... args)
{
    std::vector<std::any> a = {args ...};
}

Pretty cool.  The fact that the items are stored in std::any means that anything can be passed to it, and it can now be accessed in runtime. Note that this works because std::any is not itself a template; It stores the contained  object as a generic pointer and tests via typeinfo if it matches the type passed to it. By the way I believe that type testing should be relaxed, for example if you have a std::any that contains an int, why not returning it with any_cast<long>()?  

Now the new printf in one function (ok one would loop the vector for efficience, but now it doesn't matter):

C++
template<typename ... many>
void safe_printf2(const char *s, many ... args)
{
    vector<any> a = {args ...};

    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {

                if (a.empty())
                    throw std::logic_error("Fewer arguments provided to printf");

                if (a[0].type() == typeid(string)) cout << any_cast<string>(a[0]);
                if (a[0].type() == typeid(int)) cout << any_cast<int>(a[0]);
                if (a[0].type() == typeid(double)) cout << any_cast<double>(a[0]);

                a.erase(a.begin());
                s++;
            }
        }
        std::cout << *s++;
    }
}

 

// ----
int main()
{
 safe_printf2("Hello % how are you today? I have % eggs and your height is %","Jack"s, 32,5.7);
 // Hello Jack how are you today? I have 32 eggs and your height is 5.7
}

Generally, the use of vector<any> allows you to expand a parameter pack to runtime and manipulate it with a for loop. 

Of course, the initializer list makes a copy of all the parameters. So you may want to use pointers:

vector<any> a = {&args...};

if (a[0].type() == typeid(string*)) cout << *any_cast<string*>(a[0]);

or, references, with the aid of std::ref and std::reference_wrapper (I prefer pointers. Besides, the std::reference_wrapper takes longer to write, longer to understand and it uses internally pointers anyway since it can't use anything else. Shame on me):

vector<any> a = {std::ref(args)...};

if (a[0].type() == typeid(std::reference_wrapper<string>))  cout << any_cast<std::reference_wrapper<string>>(a[0]).get();

 

History

06 Jan 2018 : First Release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS, Android and Web (HTML/Javascript/CSS).

I 've a PhD in Digital Signal Processing and Artificial Intelligence and I specialize in Pro Audio and AI applications.

My home page: https://www.turbo-play.com

Comments and Discussions

 
Questionstd::any in run-time Pin
geoyar8-Jan-18 7:55
professionalgeoyar8-Jan-18 7:55 
AnswerRe: std::any in run-time Pin
Michael Chourdakis8-Jan-18 11:46
mvaMichael Chourdakis8-Jan-18 11:46 
GeneralRe: std::any in run-time Pin
geoyar8-Jan-18 15:16
professionalgeoyar8-Jan-18 15:16 
GeneralRe: std::any in run-time Pin
Michael Chourdakis8-Jan-18 19:02
mvaMichael Chourdakis8-Jan-18 19:02 
GeneralRe: std::any in run-time Pin
truthadjustr12-Jul-18 9:06
truthadjustr12-Jul-18 9:06 
GeneralRe: std::any in run-time Pin
Michael Chourdakis13-Jul-18 11:49
mvaMichael Chourdakis13-Jul-18 11:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.