问题描述
我从haskell和python进入C ++,那里有将数据类型转换为字符串的内置方法。
例如,在 Haskell 中,存在多态函数show
。
我有兴趣在C ++中创建一些模板功能,这些功能可以完成类似的工作。
例如,我们可以将vector<int>
转换为类似这样的字符串。
string toString(vector<int> v)
{
ostringstream o;
for (int elem: v)
o << elem << " ";
return o.str()
}
这将int
的所有字符串表示形式放在一行上。现在,如果我想以这种方式转换vector<vector<int> >
会怎样。
string toString(vector<vector<int> > v)
{
ostringstream o;
for (auto elem : v)
{
o << toString(elem) << "\n";
}
}
我的问题是:如果我想创建一个可与toString
和vector<class A>
一起使用的多态vector<vector<class A>
怎么办?我该怎么办?
我需要添加一些将类型class A
转换为std::string
的功能:我是否只为该类型提供了toString
的至少一种专业化功能?模板机制会解决所有这些问题吗?
还是已经有代码可以做到这一点?
解决方法
当前没有直接的通用方法可以执行此操作,但是您可以简单地构建自己的方法。这是一个示例程序,它将模仿您的行为。
#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;
}
输出
1 2 3 4
1 2 3
4 5 6
7 8 9
以上代码适用于vector<T>
和vector<vector<T>>
,但并非在所有情况下都适用。如果向量中有嵌套向量,则函数声明将无法识别它。另外,它不会识别其他容器,例如maps
,sets
,lists
,queues
等,然后您必须从此处生成此函数才能接受所有不同类型的容器...
这时,您将开始看到代码重复和重复模式。因此,与其将函数声明为:
template<T>
std::string toString(const std::vector<T>& vec) { /* ... */ }
您可以模板container
本身...
template<template<class> class Container,class Ty>
std::string toString(const Container<Ty>& container ) { /*... */ }
现在这对大多数容器都适用,但是对于某些容器,要使其正常工作可能会有些棘手,例如std::map
,因为它可以从std::pair
中获取值,或者可以基于其声明以及使用括号初始化的构造函数的两个相应类型。在这里您可能必须重载此特定容器的功能,但总体思路仍然适用。
这不仅仅是使用templates
,还使用templates
,因为它们的参数本身就是templates
,如果您不熟悉它们,它们的语法可能会有些令人生畏初学者。我相信您可以在template
template
参数上找到大量研究...
修改
请注意,将type
传递到Container<Ty>
时仍要小心。对于简单的内置类型,例如int
,float
,char
,double
等,这很简单...
但是,如果您有自己的用户定义的class
或struct
...
class Foo {
private:
int bar;
float baz;
public:
Foo() : bar{0},baz{0.0f} {}
Foo(int barIn,float bazIn) : bar{barIn},baz{bazIn} {}
};
然后您或其他尝试使用您的代码的人决定这样做:
std::vector<Foo> foos { Foo(1,3.5f),Foo(2,4.0f),Foo(3,3.14159f) };
std::string report = toString(foos);
以上内容并不是那么简单,因为程序或函数将不知道如何将Foo
转换为std::string
。因此,确实需要考虑到注意事项。在这里,您可能需要其他帮助程序模板函数,以将用户定义的类或结构转换为std::string
,然后您必须专门针对这些类型的toString()
函数,并在其中使用转换帮助程序功能。 ...
现在,随着C++
语言随着标准的每个发行版的发展和对各种编译器的改进而发展,事情的确趋于更加简化,也就是说,这将很快成为一种普遍现象和一种通用的重复模式。最终可能会变得精简。 C++
的未来前景乐观。已经有工具可以帮助您构建自己的工具。随着时间的流逝,这些工具变得易于使用,甚至可以简化您的代码和生产时间。
如果我想创建可与
toString
和vector<class A>
一起使用的多态vector<vector<class A>
怎么办?我该怎么办?
是的,在c++17中是可能的,通过if constexpr
功能和递归函数模板的组合(即将toString
作为递归函数模板)
在跳到通用函数模板之前,您的class A
需要实现operator<<
重载,以便std::ostringstream::operator<<
可以使用它。例如,让我们考虑
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;
}
};
现在toString
函数如下所示:
#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
}
}
现在,您可以将toString
和std::vector<std::vector<A>>
以及std::vector<A> aObjs
和std::vector< /* primitive types */ >
都调用。
(See Complete Demo Online Live)
我是否只为该类型提供
toString
的至少一种专业化?模板机制会解决所有这些问题吗?
模板专业化也是另一种选择。但是,如果您可以使用C ++ 17,我建议采用上述方式,它将对问题中提供的所有类型进行排序。