Robinson
Robinson

Reputation: 10122

Iterating a parameter pack with variadic templates in c++

As part of a logging library, I would like to be able to iterate a parameter pack, writing each value to a stream. My first attempt doesn't compile however. The first error is "error C2144: syntax error : 'int' should be preceded by '}'".

#include <sstream>
#include <ostream>
#include <iomanip>
#include <fstream>

template <typename ...Args>
std::ostream & Write(std::ostream & o, std::initializer_list<Args...> list) 
{
    size_t size = list.size();

    if(list.size() > 0)
    {
        for(size_t i = 0; i < (size - 1); i++)
            o << list[i] << ", ";

        o << list[i];
    }

    return o;
}

template<typename ...Args>
std::ostream & Write(std::ostream & o, Args...)
{
    return Write(o, { Args... });
}

int main(int argc, wchar_t * argv[])
{
    std::ostringstream o;

    Write(o, 1, "Hello", 2, "World", 3, 1.4857);

    // o should contain the string of characters "1, Hello, 2, World, 3, 1.4857"

    return 0;
}

How do I iterate each item in ... and send it to the stream?

Upvotes: 6

Views: 14827

Answers (4)

Eric
Eric

Reputation: 97565

It's possible to write a helper function to iterate over a parameter pack:

// Invoke each of the functions `f` in sequence
template<typename... F>
void invoke_all(F&&... f) {
    std::initializer_list<bool>{(f(), false)...};
}

Which in your case, can be used as:

template<typename... Args>
std::ostream& Write(std::ostream& os, Args&&... args) {
    auto joiner = std::experimental::ostream_joiner(os, ", ");
    invoke_all([&]() {
        *joiner++ = std::forward<Args>(args);
    }...);
    return os;
}

Upvotes: 3

T.C.
T.C.

Reputation: 137301

Recursion is one option:

template<typename Arg>
std::ostream & Write(std::ostream & o, Arg&& arg) { 
    return o << std::forward<Arg>(arg); 
}

template<typename Arg, typename ...Args>
std::ostream & Write(std::ostream & o, Arg&& arg, Args&&... args)
{
    o << std::forward<Arg>(arg) << ", ";
    return Write(o, std::forward<Args>(args)...);
}

Demo.

Alternatively, the pack expansion trick still works, with a little tweak - you need to special-case the first item in the list:

template<typename Arg, typename ...Args>
std::ostream & Write(std::ostream & o, Arg&& arg, Args&&... args)
{
    o << std::forward<Arg>(arg);

    using expander = int[];
    (void) expander{ (o << ", " << std::forward<Args>(args), void(), 0)... };

    return o;
}

Demo.

Upvotes: 12

Columbo
Columbo

Reputation: 60979

If an extra comma at the end is okay, use

template<typename... Args>
std::ostream& Write(std::ostream& o, Args&&... args)
{
    std::initializer_list<bool> { o << std::forward<Args>(args) << ", "... };
    return o;
}

Otherwise you will need recursion:

template <typename A>
std::ostream& Write(std::ostream& o, A&& a)
{  return o << std::forward<A>(a);  }

template <typename A, typename... Args>
std::ostream& Write(std::ostream& o, A&& a, Args&&... args)
{
    return Write(o << std::forward<A>(a) << ", ", std::forward<Args>(args)...);
}

Upvotes: 2

Sebastian Redl
Sebastian Redl

Reputation: 71899

Here's a utility I like to use:

#define VARIADIC_DETAIL_CAT2(a, b) a ## b
#define VARIADIC_DETAIL_CAT(a, b) VARIADIC_DETAIL_CAT2(a, b)

#define VARIADIC_EXPAND(...) \
    int VARIADIC_DETAIL_CAT(libutil_expando, __COUNTER__) [] = { 0, \
      ((__VA_ARGS__), 0)... \
    } \
    /**/

With this, you can write:

template<typename... Args>
std::ostream & Write(std::ostream& o, Args&&... args)
{
    VARIADIC_EXPAND(o << std::forward<Args>(args));
}

Getting correct separators is trickier.

Upvotes: 2

Related Questions