I have a particular format that requires space delimited tokens with a final null terminator (the null is part of the output). I created a function to send a series of space delimited tokens to an output stream:
// C++ variadic template function to output tokens to a stream delimited by spaces
template <typename T>
void join(std::ostream& os, T const& arg)
{
// This is the last argument, so insert a null in the stream to delimit
os << arg << '\000';
}
// Join one, add a space, try again.
template <typename T, typename... Args>
void join(std::ostream& os, T const& arg, Args... args) // recursive variadic function
{
os << arg << " ";
join(os, args...);
}
This works fine for things like join(os, 1, foo, "THING", M_PI);
. However, I also have some tokens that need to be not space-delimited, like join(os, "KEY=", val);
.
I tried to play with the arguments, thinking maybe I could stuff a nullptr
in the argument list and use that to overload the method, skipping the space, but I cannot for the life of me figure out how to do that kind of overload.
Most of the questions on variadic templates I have seen are fairly old, before standards compliance was more ubiquitous. If this has been answered elsewhere, please point me to it. I'm using GCC 7.3.
Or, just tell me I am overly complicating what should be very simple. Maybe the variadic template is not the right hammer for this bolt?
Introduce a helper
template <typename A, typename B>
struct pack_t {
const A& a;
const B& b;
};
template <typename A, typename B>
std::ostream& operator<<(std::ostream& os, pack_t<A, B> pac) {
return os << pac.a << pac.b;
}
template <typename A, typename B>
auto pack(const A& a, const B& b) noexcept {
return pack_t<A, B>{a, b};
}
Use it like
join(std::cout, "Hello", "World", pack("pi=", 3.14));
Complete code (live)
#include <iostream>
template <typename A, typename B>
struct pack_t {
const A& a;
const B& b;
};
template <typename A, typename B>
std::ostream& operator<<(std::ostream& os, pack_t<A, B> pac) {
return os << pac.a << pac.b;
}
template <typename A, typename B>
auto pack(const A& a, const B& b) noexcept {
return pack_t<A, B>{a, b};
}
template <typename T>
void join(std::ostream& os, const T& arg) {
os << arg << '\0';
}
template <typename T, typename... Args>
void join(std::ostream& os, const T& arg, const Args&... args) {
os << arg << ' ';
join(os, args...);
}
int main() {
join(std::cout, "Hello", "World", pack("pi=", 3.14));
}
Note that you can extend the helper to support more-than-two arguments by basing pack_t
on std::tuple
via aggregation or inheritance. For example,
namespace impl {
template <typename T, std::size_t... Idx>
struct pack_t {
T v;
};
template <std::size_t... Idx, typename... Ts>
auto pack(std::index_sequence<Idx...>, Ts&&... vs) noexcept {
auto v = std::forward_as_tuple(std::forward<Ts>(vs)...);
return pack_t<decltype(v), Idx...>{std::move(v)};
}
template <typename T, std::size_t... Idx>
std::ostream& operator<<(std::ostream& os, pack_t<T, Idx...> args) {
return ((os << std::get<Idx>(std::move(args.v))), ...);
}
}
template <typename... Ts>
auto pack(Ts&&... vs) noexcept {
return impl::pack(
std::index_sequence_for<Ts...>{}, std::forward<Ts>(vs)...);
}
Now, you can pack a varying number of arguments (live).
I suppose you can select a special type to skip the preceding space; by example you can define a no_space_tag
class or struct
struct no_space_tag {};
and add a join()
version that intercept a no_space_tag
object in second position (third, counting os
) and doesn't add the space after the first (second) element; I mean
template <typename T, typename ... Args>
void join (std::ostream & os, T const & arg, no_space_tag const &,
Args ... args)
{
os << arg;
join(os, args...);
}
so you can add a no_space_tag
object in this way
join(std::cout, "KEY=", no_space_tag{}, val);
or also in this way
no_space_tag nst;
join(std::cout, "KEY=", nst, val, ',', "KEY=", nst, val2);
to avoid to add the preceding space.
The following is a full working example
#include <iostream>
struct no_space_tag {};
template <typename T>
void join (std::ostream & os, T const & arg)
{ os << arg << '\000'; }
template <typename T, typename ... Args>
void join (std::ostream & os, T const & arg, Args ... args)
{
os << arg << " ";
join(os, args...);
}
template <typename T, typename ... Args>
void join (std::ostream & os, T const & arg, no_space_tag const &,
Args ... args)
{
os << arg;
join(os, args...);
}
int main()
{
char foo {'a'};
long val { 42L };
join(std::cout, 1, foo, "THING", "M_PI");
join(std::cout, "KEY=", no_space_tag{}, val);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With