如何将各种类型的向量转换为 std::string?

How do I convert vectors of various types to std::string?

我是从 and 转到 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";
   }
}

我的问题是:如果我想创建一个与vector<class A>vector<vector<class A> 一起工作的多态toString 怎么办?我该怎么做?

我需要添加一些将类型 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>>,但并非在所有情况下都适用。如果向量中有嵌套向量,函数声明将无法识别它。此外,它不会识别其他容器,例如 mapssetslistsqueues 等...从这里您必须生成此函数才能接受所有不同类型的容器...

此时,您将开始看到 code-duplication 和重复模式。因此,与其将函数声明为:

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 中获取值,或者它可以根据其声明及其使用 brace-initialization 的构造函数采用两个相应的类型。这是您可能必须为这个特定容器重载函数的地方,但总体思路仍然适用。

这不仅仅是使用 templates,它还使用 templates,其中它们的参数是 templates 本身,如果您不熟悉它们,它们的语法可能有点初学者望而生畏。我相信您可以找到大量关于 template template 参数的研究...


编辑

附带说明一下,您仍然需要小心将 type 传递给 Container<Ty>。对于简单的 built-in 类型,例如 intfloatchardouble 等。这很简单...

但是,如果您有自己的 user-defined classstruct...

怎么办?
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。因此,确实需要考虑谨慎和考虑。这是你可能需要额外的辅助模板函数来将 user-defined 类 或结构转换为 std::string 的地方,然后你必须为这些类型专门化你的 toString() 函数并使用其中的转换辅助函数...


现在,随着 C++ 语言随着每个标准的发布和对各种编译器的改进而发展,事情确实趋于变得更加简化,据说这很快就会成为普遍现象和普遍现象重复的模式最终可能会变得精简。 C++ 的未来前景乐观。已经有工具可以帮助您构建自己的工具。随着时间的推移,这些工具变得易于使用,甚至可以简化您的代码和生产时间。

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?

是的,, by the cobination of 功能和递归函数模板中是可能的(即将 toString 作为递归函数模板)。

在进入通用函数模板之前,您的 class A 需要实现 operator<< 重载 so-that 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)


Do I just provide at least one specialization of toString for that type? Does the template mechanism sort all this out?

模板专业化也是另一种选择。但是,如果您可以访问 C++17,我建议采用上述方式,它将对您在问题中提供的所有类型进行排序。