It is doable but mostly pointless as you would typically know what kind of data you have to process.
I managed to come up with something like this. It will work for every type that is iterable with for-each loop or a tuple, or has overloaded operator<<
. You can do it without C++20 features but it will be a total SFINAE mess.
#include <iostream>
#include <string>
#include <type_traits>
#include <map>
#include <list>
#include <vector>
#include <tuple>
#include <sstream>
using namespace std;
template <typename T>
concept InterableRange = requires (T a) {
std::begin(a);
std::end(a);
};
template <typename T>
concept TupleLikeType = requires (T a) {
std::tuple_size<T>();
};
template<TupleLikeType T>
string stringify(const T& c);
template<class T>
string stringify(const T& c);
template<InterableRange T>
string stringify(const T& c) {
string str = "[ ";
auto size = std::size(c);
std::size_t i = 0;
for (const auto& elem : c) {
str += stringify(elem);
if(i++ < size - 1)
str += ", ";
}
str += " ]";
return str;
}
template<TupleLikeType T>
string stringify(const T& c) {
string str = "[ ";
auto size = std::tuple_size<T>();
std::size_t i = 0;
std::stringstream input;
auto insert = [&input, size, &i](const auto& data) {
input << stringify(data);
if(i++ < size - 1)
{
input.put(',');
input.put(' ');
}
};
std::apply([&insert](const auto&... args){
(insert(args), ...);
}, c);
str += input.str();
str += " ]";
return str;
}
template<class T>
string stringify(const T& c) {
std::stringstream input;
input << c;
return input.str();
}
int main() {
map<string,vector<list<int>>> m {
{ "1", {{1,2}, {3, 4}}},
{ "2", {{10,20}, {30, 40}}}
};
cout << stringify(m);
}
It will print
[ [ [ 1 ], [ [ 1, 2 ], [ 3, 4 ] ] ], [ [ 2 ], [ [ 10, 20 ], [ 30, 40 ] ] ] ]