C++ Variadic Templates

Just experimented with those, and expanded the example from the wiki a bit to work with streams and strings instead of with char*. (behaves mostly the same, except that it doesn't consider unused arguments errors). Works pretty nicely so far, though it would have to allow argument reuse and non-space seperated arguments for me to consider it actually usuable. Fun (albeit short) exercise though.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <stdexcept>


template <typename T1, typename... Args>
void s_printf(std::ostream& out,
              const std::string& str,
              T1 arg1,
              const Args&... args);
void s_printf(std::ostream& out, const std::string& str);


int main()
{
    //example application
    std::string name;
    std::cout<<"Please enter your name: ";
    std::cin>>name;
    try
    {
        s_printf(std::cout, "Hello %! % times!", name, 100);
    } catch (std::runtime_error& e) {
        std::cerr<<"Error: "<<e.what();
    }
    std::cout<<std::endl;
    return 0;
}

template <typename T1, typename... Args>
void s_printf(std::ostream& out,
              const std::string& str,
              T1 arg1,
              const Args&... args)
{
    std::size_t last_pos = 0;
    std::size_t pos = str.find('%');
    while(pos==str.find("%%",last_pos) && pos!=std::string::npos)
    {
        out << str.substr(last_pos, (pos-last_pos)) << '%';
        last_pos = pos+2;
        pos = str.find("%",pos+2);
    }
    if(pos != std::string::npos)
    {
        out << str.substr(last_pos, (pos - last_pos));
        out << arg1;
        s_printf(out, str.substr(pos+1), args...);
    } else {
        out << str;
    }

}

void s_printf(std::ostream& out, const std::string& str)
{
    std::size_t last_pos = 0;
    std::size_t pos = str.find('%'); 
    while(pos!=std::string::npos)
    {
        if(pos != str.find("%%",last_pos))
        {
            throw std::runtime_error("More placeholders than arguments!");
        }
        out << str.substr(last_pos,(pos-last_pos)) << '%';
        last_pos = pos+2;
        pos = str.find('%');
    }
    out << str.substr(last_pos);
}


What do you think - will you use this feature? To be honest, I can't imagine too many uses for this right now. Maybe for the boost folks - I still don't get half of the template magic they've been doing.
Last edited on
What do you think - will you use this feature?


No, not until it gets streamlined to the point where you don't have to do all the work yourself.

In this regard, C-style variadic functions are better. If only because you don't need to overload a template function.
Why are they bothering with this stuff when there's still no std::string::split() method. That's something I imagine people will actually use.
Last edited on
variadic templates are great stuff, I use them in production (and comparing them to C's va_list is even more far-fetched than comparing exceptions to longjmp())

To go along with the I/O paradigm, I would make that s_printf return a reference to the output stream, and signal errors with failbit. I wouldn't use it because I already use boost.format, but it's a decent student exercise.


and comparing them to C's va_list is even more far-fetched than comparing exceptions to longjmp()


It'd be beneficial to this thread if you explained why variadic templates are better than C's va_list.

Edit: grammar.
Last edited on
va_list is severely limited even at its original purpose (making functions that take arbitrary number of arguments), because
1) only trivially-copyable types may be used (can't pass std::string)
2) special type conversions applied to non-class arguments (can't pass a float)
3) types are completely erased (and have to be communicated in some user-provided manner)
4) even argument count is lost (also has to be communicated somehow)

templates preserve types and counts, and they can be used for more than just calling functions like printf: they can forward their arguments (as in std::make_shared or vector::emplace_back), they can work with tuples, both returning them (as in std::tie) and taking them as arguments (as in std::tuple_cat), they can implement std::bind and std::mem_fn without ridiculous amounts of overloads (which was what boost had to do before C++11)
Last edited on
C#'s params object[] syntax solves all of those problems.
@Cubbi, and what about code bloat? Generating a dedicated s_printf body for each occurrence of s_prinf call is waste of resources.
@rapidcoder sure, if your requirements are within the limitations of va_list and the loss of efficiency, safety, and flexibility is worth the size difference (not "every call", by the way, every distinct type signature), it's justified. Most people choose speed over size.
Topic archived. No new replies allowed.