Search code examples
c++templatescoutvariadicparameter-pack

Print method for variadic template pairs in C++


I want to achieve something like:

export_vars("path/to/file.dat", {"variable_name", obj}, {"another_variable", 2});

where obj can be any type as long as it has an << overload - the idea is to write to an ofstream later on. I have tried (for an initializer_list of pairs):

void
export_vars(const std::string& path, std::initializer_list<std::pair<std::string, std::any>> args)
{       
    for (auto& [name, var] : args)
        std::cout << name << ": " << var << std::endl;
}

but std::any cannot be << without knowing the underlying type. Can it maybe be achieved using variadic templates and parameter pack expansion? I also tried something like:

template <class... Args>
void
export_vars(const std::string& path, Args... args)
{   
    (std::cout << ... << args.first << args.second) << std::endl;
}

but that's obviously wrong. Any suggestions?


Solution

  • {..} has no type, and so disallows most deduction.

    Several work arounds:

    • Change call to use std::pair explicitly:

      template <typename ... Pairs>
      void export_vars(const std::string&, const Pairs&... args)
      {       
          ((std::cout << args.first << ": " << args.second << std::endl), ...);
      }
      
      int main()
      {
          export_vars("unused", std::pair{"int", 42}, std::pair{"cstring", "toto"});
      }
      

      Demo

    • Don't use template:

      void export_vars(const std::string&,
                       const std::initializer_list<std::pair<std::string, Streamable>>& args)
      {
          for (const auto& [name, value] : args) {
              std::cout << name << ": " << value << std::endl;
          }
      }
      int main()
      {
          export_vars("unused", {{"int", 42}, {"cstring", "toto"}});
      }
      

      with Streamable using type-erasure, possibly something like:

      class Streamable
      {
          struct IStreamable
          {
              virtual ~IStreamable() = default;
              virtual void print(std::ostream&) = 0;
          };
      
          template <typename T>
          struct StreamableT : IStreamable
          {
              StreamableT(T t) : data(std::forward<T>(t)) {}
              virtual void print(std::ostream& os) { os << data; }
      
              T data;
          };
      
          std::unique_ptr<IStreamable> ptr;
      public:
          template <typename T>
          // Possibly some concepts/SFINAE as requires(is_streamable<T>)
          Streamable(T&& t) : ptr{std::make_unique<StreamableT<std::decay_t<T>>>(t)} {}
      
          friend std::ostream& operator << (std::ostream& os, const Streamable& streamable)
          {
              streamable.ptr->print(os);
              return os;
          } 
      };
      

      Demo