Search code examples
c++templatesstdvectorstdstringfunction-templates

How do I convert vectors of various types to std::string?


I am coming to C++ from and where there are built-in ways of converting data types to strings.

For example, in Haskell there is the polymorphic function show.

I am interested in creating some template functions in C++ that would do something similar.

For instance, we could convert vector<int> to a string something like this.

string toString(vector<int> v)
{
    ostringstream o;
    for (int elem: v)
        o << elem << " ";
    return o.str()
}

This puts a string representation of the ints all on a line. Now, what if I wanted to convert a vector<vector<int> > in this way.

string toString(vector<vector<int> > v)
{
   ostringstream o;
   for (auto elem : v)
   {
      o << toString(elem) << "\n";
   }
}

My question is: what if I wanted to create a polymorphic toString that works with vector<class A> and vector<vector<class A>? How would I go about this?

I would need to add some functionality for converting type class A to a std::string: do I just provide at least one specialization of toString for that type? Does the template mechanism sort all this out?

Or is there code to do this already?


Solution

  • What if I wanted to create a polymorphic toString that works with vector<class A> and vector<vector<class A>? How would I go about this?

    Yes it is possible in , by the cobination of if constexpr feature and a recursive function template (i.e. making the toString as recursive function template).

    Before jumping into the generic function template, your class A needs to implement operator<< overload so-that std::ostringstream::operator<< can make use of it. For instance, lets consider

    struct A
    {
       char mChar;
       // provide a overload for operator<< for the class!
       friend std::ostream& operator<<(std::ostream& out, const A& obj) /* noexcept */ {
          return out << obj.mChar;
       }
    };
    

    Now the toString function would look like something as follows:

    #include <type_traits> // std::is_floating_point_v, std::is_integral_v, std::is_same_v
                           // std::remove_const_t, std::remove_reference_t
    
    template<typename Type>
    inline static constexpr bool isAllowedType = std::is_floating_point_v<Type>
    || std::is_integral_v<Type> 
    || std::is_same_v<A, Type>;
    //^^^^^^^^^^^^^^^^^^^ --> struct A has been added to the
    //                        allowed types(i.e types who has operator<< given)
    
    template<typename Vector>
    std::string toString(const Vector& vec) /* noexcept */
    {
       std::ostringstream stream; 
       // value type of the passed `std::vector<Type>`
       using ValueType = std::remove_const_t< 
          std::remove_reference_t<decltype(*vec.cbegin())>
       >;
       // if it is allowed type do  concatenation!
       if constexpr (isAllowedType<ValueType>) 
       {
          for (const ValueType& elem : vec)
             stream << elem << " ";
     
          stream << '\n';
          return stream.str();
       }
       else
       {
          // otherwise do the recursive call to toString
          // for each element of passed vec
          std::string result;
          for (const ValueType& innerVec : vec)
             result += toString(innerVec);
    
          return result; // return the concatenated string
       }   
    }
    

    Now you can call the toString to the std::vector<std::vector<A>> as well as std::vector<A> aObjs, and to the std::vector< /* primitive types */ > too.

    (See Complete Demo Online Live)


    Do I just provide at least one specialization of toString for that type? Does the template mechanism sort all this out?

    Template specialization is another option too. However, if you have access to C++17, I would suggest the above manner, which will sort all of the types you provided in the question.