Variadic Recursive Template

  • A+
Category:Languages

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

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: