【问题标题】:How do I convert vectors of various types to std::string?如何将各种类型的向量转换为 std::string?
【发布时间】:2020-08-29 01:51:28
【问题描述】:

我从 开始使用 C++,那里有将数据类型转换为字符串的内置方法。

例如,在Haskell中有一个多态函数show

我有兴趣在 C++ 中创建一些类似的模板函数。

例如,我们可以将vector<int> 转换为类似这样的字符串。

string toString(vector<int> v)
{
    ostringstream o;
    for (int elem: v)
        o << elem << " ";
    return o.str()
}

这会将ints 的字符串表示形式全部放在一行上。现在,如果我想以这种方式转换 vector&lt;vector&lt;int&gt; &gt; 怎么办。

string toString(vector<vector<int> > v)
{
   ostringstream o;
   for (auto elem : v)
   {
      o << toString(elem) << "\n";
   }
}

我的问题是:如果我想创建一个与vector&lt;class A&gt;vector&lt;vector&lt;class A&gt; 一起使用的多态toString 怎么办?我该怎么办?

我需要添加一些功能来将类型class A 转换为std::string:我是否只为该类型提供至少一个toString 的特化?模板机制能解决这一切吗?

或者是否已经有代码可以做到这一点?

【问题讨论】:

  • 如果您只是将int 替换为通用T,即模板化您拥有的两个函数,那应该可以工作。
  • “多态”是指类,而不是函数。更好的词选择是“a generic toString”。
  • 通过明智地使用模板和技术(如 SFINAE)或 C++20 中的概念,可以在特定范围内执行此类操作。这些都是相当高级的、深入的话题。对学习 C++ 比较陌生的人可能还没有准备好应对这些野兽,继续学习这种当今使用的最复杂的通用编程语言会更有成效,直到上述技术成为第二天性(不,没有代码已经这样做了)。
  • 你能添加一些你想工作的示例代码吗?
  • @JaMiT 那只是 C++ 吗?因为我的理解是 Haskell 将泛型函数称为多态,也许其他语言也是如此。

标签: haskell python c++ templates stdvector stdstring function-templates


【解决方案1】:

如果我想创建一个与vector&lt;class A&gt;vector&lt;vector&lt;class A&gt; 一起使用的多态toString 怎么办?我该怎么办?

是的 中,通过if constexpr 功能和递归函数模板的组合(即使toString 作为递归函数模板)。

在进入通用函数模板之前,您的class A 需要实现operator&lt;&lt; 重载,以便std::ostringstream::operator&lt;&lt; 可以使用它。例如,让我们考虑

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
   }   
}

现在您可以将toStringstd::vector&lt;std::vector&lt;A&gt;&gt; 以及std::vector&lt;A&gt; aObjsstd::vector&lt; /* primitive types */ &gt; 联系。

(See Complete Demo Online Live)


我是否只为该类型提供至少一个toString 的专业化?模板机制能解决这一切吗?

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

【讨论】:

    【解决方案2】:

    目前没有直接通用的方法来执行此操作,但您可以简单地构建自己的方法。这是一个示例程序,它将模仿您所追求的行为。

    #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&lt;T&gt;vector&lt;vector&lt;T&gt;&gt;,但并非在所有情况下都适用。如果向量中有嵌套向量,函数声明将无法识别它。此外,它不会识别其他容器,例如mapssetslistsqueues 等......然后你必须从这里生成这个函数来接受所有不同类型的容器。 .

    此时,您将开始看到代码重复和重复模式。所以不要将函数声明为:

    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,如果你不熟悉它们,它们的语法对于初学者来说可能有点令人生畏。我相信你可以找到大量关于templatetemplate参数的研究......


    编辑

    作为旁注,您仍然必须小心 type 被传递到 Container&lt;Ty&gt;。对于简单的内置类型,例如intfloatchardouble 等,这是直截了当的...

    但是,如果您有自己的用户定义 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。因此,确实需要考虑谨慎和考虑。这是您可能需要额外的帮助模板函数来将用户定义的类或结构转换为std::string 的地方,然后您必须针对这些类型专门化您的toString() 函数并在其中使用转换帮助函数...


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

    【讨论】:

    • 在 C++17 中看起来 有可能(在旧版本中代码会多一些)。
    • @JeJo 随着C++ 语言标准的每次迭代以及编译器的改进,事情往往会变得更加简单......例如,在c++11 之前,我们本来有@ 987654357@ 现在我们可以轻松做到:for(auto &amp;e : vec) { std::cout &lt;&lt; e &lt;&lt; ' ';}。因此,在考虑 C++20 和未来版本时,请重新审视您的猜想。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-26
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多