简体   繁体   中英

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 int s 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?

There is no current direct generic way to do this but you can simply build your own. Here's a sample program that will mimic the behavior that you are after.

#include <exception>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

template<typename T>
std::string toString(const std::vector<T>& vec) {
    std::ostringstream stream;
    for (auto& elem : vec) {
        stream << elem << " ";
    }
    stream << '\n';
    return stream.str();
}

template<typename T>
std::string toString(const std::vector<std::vector<T>>& vec) {
    std::ostringstream stream;
    for (auto& elem : vec) {
        stream << toString(elem);
    }
    stream << '\n';
    return stream.str();
}


int main() {
    try {
        std::vector<int> valuesA{ 1, 2, 3, 4 };
        std::cout << toString(valuesA) << '\n';

        std::vector<std::vector<float>> valuesB { {1.0f, 2.0f, 3.0f},
                                                  {4.0f, 5.0f, 6.0f},
                                                  {7.0f, 8.0f, 9.0f}
                                                };
        std::cout << toString(valuesB) << '\n';
    } catch( const std::exception& e ) {
        std::cerr << "Exception Thrown: " << e.what() << std::endl;
        return EXIT_FAILURE;
    } catch( ... ) {
        std::cerr << __FUNCTION__ << " Caught Unknown Exception" << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Output

1 2 3 4

1 2 3
4 5 6
7 8 9

The above code will work for vector<T> and vector<vector<T>> but it will not work in every situation. If you have a nested vector within a vector, the function declaration will not recognize it. Also, it will not recognize other containers such as maps , sets , lists , queues , etc... from here you would then have to generate this function to accept all the different types of containers...

At this point, you will begin to see code-duplication and repetitive patterns. So instead of declaring the function as:

template<T>
std::string toString(const std::vector<T>& vec) { /* ... */ }

You could template the container itself...

template<template<class> class Container, class Ty>
std::string toString(const Container<Ty>& container ) { /*... */ }

Now this will work for most containers, but some containers it can be a bit tricky to get it to work properly such as std::map because it can take values from an std::pair , or it can take two corresponding types based on its declaration in conjunction with its constructors that use brace-initialization. This is where you might have to overload the function for this specific container, but the general idea still applies.

This is more than just using templates it is also using templates where their arguments are templates themselves and if you are not familiar with them, their syntax can be a bit daunting for a beginner. I'm sure you can find plenty of research on template template parameters...


Edit

As a side note, you still have to be careful with the type being passed into Container<Ty> . For simple built-in types such as int , float , char , double , etc. this is straight forward...

However, what if you have your own user-defined class or struct ...

class Foo {
private:
    int bar;
    float baz;
public:
    Foo() : bar{0}, baz{0.0f} {}
    Foo(int barIn, float bazIn) : bar{barIn}, baz{bazIn} {}
};

Then you or someone else who is trying to use your code decides to do:

std::vector<Foo> foos { Foo(1, 3.5f), Foo(2, 4.0f), Foo(3, 3.14159f) };
std::string report = toString(foos);

The above isn't so trivial because the program or the functions will not know how to convert Foo to std::string . So care and consideration does need to be taken into account. This is where you might need additional helper templated functions to convert user-defined classes or structures to an std::string , then you would have to specialize your toString() function for those types and use the conversion helper function within it...


Now, as the C++ language evolves with each release of the standard and improvements to various compilers, things do tend to become more simplified, with that being said this will soon become a common occurrence and a common repetitive pattern that may eventually become streamlined. There is a positive outlook for the future of C++ . There are already tools out there to assist you in building your own. As time progresses these tools become easily accessible to use and can even simplify your code and production time.

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 (ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM