I am trying to represent a tree-like recursive data structure where each node may be one of two different data types. I employ the boost variant to "house" the two types that might be present at each node.
However, I run into a problem. I'm declaring all these types strictly with 'using' directives, so when I get to the recursive nature of a node, it fails since typedef/using may not use recursion.
How to accomplish this?
using LeafData = int; // just for illustration
using LeafNode = std::unordered_map<std::string, LeafData>;
using InnerNode = std::unordered_map<std::string, boost_variant<InnerNode, LeafNode>>; // this is problematic since I'm using InnerNode recursively
I have explored using the boost::make_recursive_variant but the type it creates (A) is not quite what I need, as that makes a variant within a variant when instead I want a (B) single variant consisting of either an InnerNode or LeafNode.
(A) boost_variant<boost_variant<InnerNode, LeafNode>, LeafNode>
(B) boost_variant<InnerNode, LeafNode>
Straight Answer (see below for tips)
You can do what you want you want using make_recursive_variant
:
Live On Coliru
#include <boost/variant.hpp>
#include <unordered_map>
struct LeafData {
int _i;
LeafData(int i) : _i(i) {}
}; // just for illustration
using LeafNode = std::unordered_map<std::string, LeafData>;
using Node = boost::make_recursive_variant<
LeafNode,
std::unordered_map<std::string, boost::recursive_variant_>
>::type;
using Inner = std::unordered_map<std::string, Node>;
int main() {
Node tree = Inner {
{ "a", LeafNode { { "one", 1 }, { "two", 2 }, { "three",3 } } },
{ "b", Inner {
{ "b1", LeafNode { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
{ "b2", LeafNode { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
}
},
{ "c", LeafNode {} },
};
}
TIPS
Why distinguish inner/leaf nodes at all? Seems to me leaf nodes are just nodes with a value instead of children:
Live On Coliru
#include <boost/variant.hpp>
#include <unordered_map>
struct Data {
int _i;
Data(int i) : _i(i) {}
}; // just for illustration
using Tree = boost::make_recursive_variant<
Data,
std::unordered_map<std::string, boost::recursive_variant_>
>::type;
using Node = std::unordered_map<std::string, Tree>;
int main() {
Tree tree = Node {
{ "a", Node { { "one", 1 }, { "two", 2 }, { "three",3 } } },
{ "b", Node {
{ "b1", Node { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
{ "b2", Node { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
}
},
{ "c", Node {} },
};
}
Without Make-Recursive-Variant
You can with a well judged forward declare:
Live On Coliru
#include <boost/variant.hpp>
#include <unordered_map>
struct Data {
int _i;
Data(int i) : _i(i) {}
}; // just for illustration
struct Node;
using Tree = boost::variant<Data, boost::recursive_wrapper<Node> >;
struct Node : std::unordered_map<std::string, Tree> {
using base = std::unordered_map<std::string, Tree>;
using base::base; // inherit constructor
};
int main() {
Tree tree = Node {
{ "a", Node { { "one", 1 }, { "two", 2 }, { "three",3 } } },
{ "b", Node {
{ "b1", Node { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
{ "b2", Node { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
}
},
{ "c", Node {} },
};
}
More Elegant + More Efficient
If you use an unordered_map
that can be instantiated when the mapped-type is still incomplete¹, you don't need the performance hit of recursive_wrapper
at all.
In the process, we can make the constructor smarter and the tree construction even more succinct:
Live On Coliru
#include <boost/variant.hpp>
#include <boost/unordered_map.hpp>
struct Data {
int _i;
Data(int i = 0) : _i(i) {}
}; // just for illustration
struct Node : boost::variant<Data, boost::unordered_map<std::string, Node> > {
using Map = boost::unordered_map<std::string, Node>;
using Base = boost::variant<Data, Map>;
using Base::variant;
using Base::operator=;
Node(std::initializer_list<Map::value_type> init) : Base(Map(init)) {}
};
int main() {
auto tree = Node {
{ "a", { { "one", 1 }, { "two", 2 }, { "three", 3 } } },
{ "b", {
{ "b1", { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
{ "b2", { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
}
},
{ "c", {} },
};
}
¹ (I think c++17 added that to the standard library specs)
I was able to get the boost::make_recursive_variant approach to work, but it required an additional handler to the variant visitor.
using LeafData = int; // just for illustration
using LeafNode = std::unordered_map<std::string, LeafData>;
using InnerNode = std::unordered_map<std::string, boost::make_recursive_variant<boost::recursive_variant_, LeafNode>::type>;
The variant visitor required handling three types, LeafNode, InnerNode, AND, the InnerNode::mapped_type (which is the recursive variant), then the compiler seemed happy. Not sure if this will "work" though, so need to run and see how behaves.