There are a number of problems:
- doing
storedString += i;
(where i
is int
) doesn't do what you think it does
- You fail to return a value from
operator()(std::string const&) const
- You recurse into
create_string()
which is a temporary; this is the reason you couldn't make things const
properly
Fixing all that and doing it right with the 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";
}
Prints:
Result '[ 1, , hello world, , [ 42, , [ some more, ], -42, ] ]'
Improving
You can do a lot better.
First you can reduce allocations by using a stream instead of copying strings. This could also be made to make the visitor stateless.
You can also make the visitor apply directly to variants, so you don't have to invoke apply_visitor
anymore.
You can avoid the redundant commas and use std::quoted
to properly escape the string values (in case they contain "
, [
, ]
, ,
)
You can make everything generic so it works with a lot more variants
Here's the implementation of these bullets:
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);
}
Printing
[ 1, "hello world", [ 42, [ "some more" ], -42 ] ]
Even More: Pretty Indenting
Now that we've lost the baggage of the string, we can add some state to the visitor again. Let's make it pretty print with indentation.
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 << "]";
}
};
Prints
[
1,
"hello world",
[],
[
42,
[
"some more"
],
-42
]
]
For The Win: Without Recursive Variant, Neat Literals
I noticed you wrote your pseudo-code more like JSON:
ObjectE t = ["string" , [1, 2] , ["str2", "str3"], [1,3,6], [[1,2], [1,4]]]
That's not C++. However, I can make it work like this:
ObjectE t = {"string" , {1, 2} , {"str2", "str3"}, {1,3,6}, {{1,2}, {1,4}}};
You do so by making a type derived from variant, and adding a constructor that takes an initializer list for the vector element:
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)) {}
};
That's all. The improved visitor above was generic enough from the start, so we don't need to change another line of code:
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);
Prints
[
1,
"hello world",
[],
[
42,
[
"some more"
],
-42
]
]
t: [
"string",
[
1,
2
],
[
"str2",
"str3"
],
[
1,
3,
6
],
[
[
1,
2
],
[
1,
4
]
]
]