I am coming to C++ from haskell and python 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?
What if I wanted to create a polymorphic
toString
that works withvector<class A>
andvector<vector<class A>
? How would I go about this?
Yes it is possible in c++17, 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.