如何将 Boost::make_recursive_variant 对象转换为字符串?

How to convert a Boost::make_recursive_variant Object into a string?

一直在玩 Boost:make_recursive_variant,我对如何从给定的 Variant 和 return 创建字符串感到很困惑。

我可以使用 cout 轻松输出,但我的目标是创建一个 C++ 版本的 Arrays.deeptoString 从 java 到 return 一个字符串。将 运行 纳入试图解决 recursive_variant 的编译问题。这是我的 Arrays.deeptoString 当前代码。

typedef boost::make_recursive_variant<string, int, vector<boost::recursive_variant_ > >::type ObjectE;
class deepToString : public boost::static_visitor<std::string> {
public:
    string operator()(const int i) const {
        storedString += i;
        storedString += ",";
        return storedString;
    }
    string operator()(std::vector<ObjectE> const &v) const {
        storedString += "[";
        for (std::size_t i = 0; i < v.size() - 1; i++) {
            storedString += boost::apply_visitor(create_string(), v[i]);
            storedString += ",";
        }
        storedString += boost::apply_visitor(create_string(), v[v.size() - 1]);
        storedString += "]";
        return storedString;
    }
    string operator()(std::string const &s) const {
        storedString += s;
        storedString += ",";
    }
    mutable string storedString = "";
};

我希望我可以 return 字符串,但它却让我崩溃了。我...既喜欢又讨厌递归变体 atm。理想情况下,如果有给定的

ObjectE t = ["string" , [1, 2] , ["str2", "str3"], [1,3,6], [[1,2], [1,4]]]

我应该可以执行以下行来获取

string output = boost::apply_visitor(deepToString(), t);

输出得到

 "["string" , [1, 2] , ["str2", "str3"], [1,3,6], [[1,2], [1,4]]]"

一开始,我尝试不使用 const 方法,因为我们正在改变方法内部的值,但我遇到了一大堆编译错误。当前的样式适合我,但会在我身上崩溃。我应该先调查一下它崩溃的原因。

关于我应该如何解决这个问题的任何想法

存在一些问题:

  1. storedString += i;(其中 iint)并没有按照您的想法去做
  2. 您未能 return 来自 operator()(std::string const&) const
  3. 的值
  4. 你递归到 create_string() 这是一个临时的;这就是你无法 const 正确
  5. 的原因

修复所有问题并使用 mutable/const:

Live On Coliru

#include <boost/variant.hpp>
#include <vector>
#include <iostream>

typedef boost::make_recursive_variant<std::string, int, std::vector<boost::recursive_variant_> >::type ObjectE;
typedef std::vector<ObjectE> ObjectV;

struct create_string : boost::static_visitor<std::string const&> {
    std::string const& operator()(const int i) {
        return storedString += std::to_string(i) + ", ";
    }
    std::string const& operator()(ObjectV const &v) {
        storedString += "[ ";
        for (std::size_t i = 0; i < v.size() - 1; i++) {
            create_string nest;
            storedString += boost::apply_visitor(nest, v[i]) + ", ";
        }
        create_string nest;
        storedString += boost::apply_visitor(nest, v[v.size() - 1]);
        return storedString += " ]";
    }
    std::string const& operator()(std::string const &s) {
        return storedString += s + ", ";
    }

    std::string storedString = "";
};

int main() {
    ObjectE obj = ObjectV { 
        1,
        "hello world",
        ObjectV {
            42, ObjectV { "some more" }, -42
        },
    };

    create_string vis;

    std::cout << "Result '" << boost::apply_visitor(vis, obj) << "'\n";
}

打印:

Result '[ 1, , hello world, , [ 42, , [ some more,  ], -42,  ] ]'

改善

你可以做得更好。

  • 首先,您可以通过使用流而不是复制字符串来减少分配。这也可以使访问者成为无国籍人。

  • 您也可以让访问者直接申请变体,这样您就不必再调用apply_visitor

  • 您可以避免多余的逗号并使用 std::quoted 正确转义字符串值(以防它们包含 "[], ,)

  • 您可以使所有内容通用,以便它适用于更多变体

下面是这些项目符号的实现:

Live On Coliru

#include <boost/variant.hpp>
#include <vector>
#include <iostream>
#include <iomanip>

typedef boost::make_recursive_variant<std::string, int, std::vector<boost::recursive_variant_> >::type ObjectE;
typedef std::vector<ObjectE> ObjectV;

struct printer {
    using result_type = void;
    std::ostream& _os;

    // forwards for `operator()`
    template <typename T> void call(T const& v) const { return operator()(v); }

    // dispatch for variants
    template <typename... Ts> void operator()(boost::variant<Ts...> const& v) const {
        return boost::apply_visitor(*this, v);
    }

    void operator()(int i) const                { _os << i;              } 
    void operator()(std::string const &s) const { _os << std::quoted(s); } 

    template <typename... Ts> void operator()(std::vector<Ts...> const& v) const {
        _os << "[ ";
        bool first = true;
        for (auto& el : v) {
            if (first) first = false;
            else       _os << ", ";
            call(el);
        }
        _os << " ]";
    }
};

int main() {
    ObjectE obj = ObjectV { 
        1,
        "hello world",
        ObjectV {
            42, ObjectV { "some more" }, -42
        },
    };

    printer print{std::cout};
    print(obj);
}

打印

[ 1, "hello world", [ 42, [ "some more" ], -42 ] ]

更多:漂亮的缩进

现在我们已经丢失了字符串的包袱,我们可以再次为访问者添加一些状态。让我们用缩进让它打印得漂亮。

Live On Coliru

struct printer {
    using result_type = void;
    std::ostream& _os;
    std::string _indent = "";

    template <typename... Ts> void operator()(boost::variant<Ts...> const& v) const {
        return boost::apply_visitor(*this, v);
    }

    void operator()(int i) const                { _os << i;              } 
    void operator()(std::string const &s) const { _os << std::quoted(s); } 

    template <typename... Ts> void operator()(std::vector<Ts...> const& v) const {
        _os << "[";
        auto indent = _indent + "    ";

        bool first = true;
        for (auto& el : v) {
            if (first) first = false;
            else _os << ",";
            _os << "\n" << indent;
            printer{_os, indent}(el);
        }

        if (!v.empty())
            _os << "\n" << _indent;
        _os << "]";
    }
};

版画

[
    1,
    "hello world",
    [],
    [
        42,
        [
            "some more"
        ],
        -42
    ]
]

为了胜利:没有递归变体,整洁的文字

我注意到你写的伪代码更像 JSON:

ObjectE t = ["string" , [1, 2] , ["str2", "str3"], [1,3,6], [[1,2], [1,4]]]

那不是 C++。但是,我可以让它像这样工作:

ObjectE t = {"string" , {1, 2} , {"str2", "str3"}, {1,3,6}, {{1,2}, {1,4}}};

您可以通过创建一个从变体派生的类型,并添加一个构造函数来实现这一点,该构造函数采用向量元素的初始化列表:

struct ObjectE : boost::variant<std::string, int, std::vector<ObjectE> > {
    using Base = boost::variant<std::string, int, std::vector<ObjectE> >;

    using Base::variant;
    using Base::operator=;

    ObjectE(std::initializer_list<ObjectE> init) : Base(std::vector<ObjectE>(init)) {}
};

就是这样。上面改进后的访问器从一开始就是通用的 enough,所以我们不需要再改一行代码:

Live On Coliru

printer print{std::cout};
print(ObjectE { 1, "hello world", {}, { 42, { "some more" }, -42 } });

std::cout << "\nt: ";

ObjectE t = {"string" , {1, 2} , {"str2", "str3"}, {1,3,6}, {{1,2}, {1,4}}};
print(t);

版画

[
    1,
    "hello world",
    [],
    [
        42,
        [
            "some more"
        ],
        -42
    ]
]
t: [
    "string",
    [
        1,
        2
    ],
    [
        "str2",
        "str3"
    ],
    [
        1,
        3,
        6
    ],
    [
        [
            1,
            2
        ],
        [
            1,
            4
        ]
    ]
]