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.