Click here to Skip to main content
13,796,233 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

5.4K views
7 bookmarked
Posted 6 Jan 2018
Licenced CPOL

C++ 17 variadic template pack to runtime

, 7 Jan 2018
Rate this:
Please Sign up or sign in to vote.
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:

 

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):

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)

Share

About the Author

Michael Chourdakis
Engineer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS and Android.

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

My home page: http://www.michaelchourdakis.com

You may also be interested in...

Comments and Discussions

 
Questionstd::any in run-time Pin
geoyar8-Jan-18 8:55
membergeoyar8-Jan-18 8:55 
AnswerRe: std::any in run-time Pin
Michael Chourdakis8-Jan-18 12:46
mvpMichael Chourdakis8-Jan-18 12:46 
GeneralRe: std::any in run-time Pin
geoyar8-Jan-18 16:16
membergeoyar8-Jan-18 16:16 
GeneralRe: std::any in run-time Pin
Michael Chourdakis8-Jan-18 20:02
mvpMichael Chourdakis8-Jan-18 20:02 
GeneralRe: std::any in run-time Pin
truthadjustr12-Jul-18 10:06
membertruthadjustr12-Jul-18 10:06 
GeneralRe: std::any in run-time Pin
Michael Chourdakis13-Jul-18 12:49
mvpMichael Chourdakis13-Jul-18 12: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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.181207.3 | Last Updated 7 Jan 2018
Article Copyright 2018 by Michael Chourdakis
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid