[英]How do I convert vectors of various types to std::string?
I am coming to C++ from haskell and python where there are built-in ways of converting data types to strings.我从haskell和python来到 C++,其中有将数据类型转换为字符串的内置方法。
For example, in Haskell there is the polymorphic function show
.例如,在Haskell 中有一个多态函数show
。
I am interested in creating some template functions in C++ that would do something similar.我有兴趣在 C++ 中创建一些可以做类似事情的模板函数。
For instance, we could convert vector<int>
to a string something like this.例如,我们可以将vector<int>
转换为类似这样的字符串。
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.这会将int
的字符串表示全部放在一行上。 Now, what if I wanted to convert a vector<vector<int> >
in this way.现在,如果我想以这种方式转换vector<vector<int> >
怎么办。
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>
?我的问题是:如果我想创建一个与vector<class A>
和vector<vector<class A>
一起使用的多态toString
怎么办? 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?我需要添加一些功能来将类型class A
转换为std::string
:我是否只为该类型提供至少一种toString
? 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.上面的代码适用于vector<T>
和vector<vector<T>>
但它不适用于所有情况。 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...此外,它不会识别其他容器,例如maps
、 sets
、 lists
、 queues
等......从这里你必须生成这个函数来接受所有不同类型的容器......
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...你可以模板container
本身......
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.现在这将适用于大多数容器,但有些容器让它正常工作可能有点棘手,例如std::map
因为它可以从std::pair
获取值,或者它可以基于两个相应的类型它的声明及其使用大括号初始化的构造函数。 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.这不仅仅是使用templates
,它还使用templates
,其中的参数本身就是templates
,如果您不熟悉它们,它们的语法对于初学者来说可能有点令人生畏。 I'm sure you can find plenty of research on template
template
parameters...我相信你可以找到大量关于template
template
参数的研究......
Edit编辑
As a side note, you still have to be careful with the type
being passed into Container<Ty>
.作为旁注,您仍然必须小心传递到Container<Ty>
的type
。 For simple built-in types such as int
, float
, char
, double
, etc. this is straight forward...对于简单的内置类型,如int
、 float
、 char
、 double
等,这是直接的......
However, what if you have your own user-defined class
or struct
...但是,如果您有自己的用户定义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} {}
};
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
.以上并不是那么简单,因为程序或函数不知道如何将Foo
转换为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...这是您可能需要额外的辅助模板化函数来将用户定义的类或结构转换为std::string
,然后您必须为这些类型专门化您的toString()
函数并在其中使用转换辅助函数...
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.现在,随着C++
语言随着标准的每个版本和对各种编译器的改进而发展,事情确实趋于变得更加简化,据说这将很快成为一种普遍现象,一种常见的重复模式,最终可能会变得流线型。 There is a positive outlook for the future of C++
. 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 withvector<class A>
andvector<vector<class A>
?如果我想创建一个与vector<class A>
和vector<vector<class A>
一起使用的多态toString
怎么办? 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 (ie making the toString
as recursive function template).是的,在c++17 中,通过if constexpr
功能和递归函数模板的组合(即,将toString
作为递归函数模板)是可能的。
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.在跳转到泛型函数模板之前,您的class A
需要实现operator<<
重载,以便std::ostringstream::operator<<
可以使用它。 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:现在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
}
}
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.现在您可以将toString
调用到std::vector<std::vector<A>>
以及std::vector<A> aObjs
,以及std::vector< /* primitive types */ >
。
( See Complete Demo Online Live ) (见完整演示在线直播)
Do I just provide at least one specialization of
toString
for that type?我是否只为该类型提供至少一种toString
? 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.但是,如果您可以访问 C++17,我会建议采用上述方式,这将对您在问题中提供的所有类型进行排序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.