Reputation: 1819
abstract: I am looking to overload the apply_visitor() method in a class that contains a recursive boost::variant object.
In the code included below there is the method:
template <typename T>
ostream& apply_visitor(const T& fn) const
I would like to overload this method for different visitors. Something like this:
ostream& apply_visitor(const PrintData& fn) const
But the problem is that the class PrintData is not yet complete (see comments in the code below). It is defined after the Node class. So I have two questions (among others -- I would welcome a general critique on this code which is modeling something I'd like to put into production).
1) Is there a way to get apply_visitor(PrintData&) to work?
2) Can I rearrange the (recursive) variant so all visitor methods are in PrintData and I wouldn't have to add apply_visitor to the Node class?
/**
* compiled with gcc (tested with 4.7.2 on linux)
* requires the boost development headers to be discoverable
* by the compiler.
*
* g++ -otest-recursive-visit -std=c++11 test-recursive-visit.cpp
* ./test-recursive-visit
**/
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include "boost/variant.hpp"
#include "boost/variant/recursive_wrapper.hpp"
#include "boost/variant/static_visitor.hpp"
using namespace std;
/// type name demangler (as implemented in g++). For other compilers,
/// we could add more #elif statements, but for now, just return
/// the mangled name.
#ifdef __GNUG__ /// compiler is g++
#include <cxxabi.h>
string type_demangle(const string& name)
{
int status;
char* res = abi::__cxa_demangle(
name.c_str(), NULL, NULL, &status);
string demangled_name((status==0) ? res : name);
free(res);
return demangled_name;
}
#else /// compiler is not g++
string type_demangle(const string& name) { return name; }
#endif
/// forward declaration of the Node class
/// (needed for recursive variant type)
class Node;
/// the actual recursive variant type
/// (typically hidden from the users)
typedef boost::variant<
boost::recursive_wrapper<Node>,
vector<int>,
vector<float>
> data_t;
// forward declaration for PrintData. See note below concerning
// Node::apply_visitor()
class PrintData;
/// this is the object users will see
/// for prototyping, the tree object is public
class Node
{
public:
/// sub-nodes are identified by unique strings
/// which point to one of the objects that data_t
/// can hold
map<string,data_t> tree;
/// constructor for a std::map object, passed to tree
Node(const initializer_list<pair<const string,data_t>>& l)
: tree(l)
{}
//
// INTERESTING PART OF THIS EXAMPLE IS HERE
//
// I tried to replace T& with PrintData& adding
// a forward declaration at the top but I get the
// errors:
//
// line 86:
// invalid use of incomplete type ‘const class PrintData’
// line 53:
// forward declaration of ‘const class PrintData’
//
/// This is called by boost::apply_visitor(Visitor(),Node)
//ostream& apply_visitor(const PrintData& fn) const
template <typename T>
ostream& apply_visitor(const T& fn) const
{
for (auto i : tree)
{
*fn.os << fn.prefix << i.first;
i.second.apply_visitor(fn);
}
return *fn.os;
}
};
/// the printing visitor to ostream object
class PrintData : public boost::static_visitor<ostream&>
{
public:
ostream* os;
string prefix;
/// keep a pointer to the ostream and keep
/// a prefix string which will hold and "indent"
/// which is something like " " for every level
/// of recursion
PrintData(ostream& out_stream, const string& prefix_str="")
: os(&out_stream)
, prefix(prefix_str)
{}
/// recurse into the tree, adding indent characters to prefix
ostream& operator()(Node& n) const
{
*os << endl;
n.apply_visitor(PrintData(*os, prefix+" "));
*os;
}
/// actual data types that we want to print out
template <typename T>
ostream& operator()(const vector<T>& d) const
{
*os << " (vector<" << type_demangle(typeid(T).name()) << ">):";
for (T i : d)
{
*os << " " << i;
}
*os << endl;
return *os;
}
};
/// convenience operator to allow: cout << node;
ostream& operator<<(ostream& os, const Node& n)
{
return boost::apply_visitor(PrintData(os), n);
}
int main()
{
/// hooray for initialization lists!!!
Node n {
{"X", Node{
{"a", vector<int>{1,2,3}},
{"b", vector<float>{2,3,4}}
}},
{"Y", vector<int>{3,4,5}},
{"Z", Node{
{"A", Node{
{"c", vector<float>{4,5,6}}
}}
}}
};
/**
applying PrintData to n prints out the following:
X
a (vector<int>): 1 2 3
b (vector<float>): 2 3 4
Y (vector<int>): 3 4 5
Z
A
c (vector<float>): 4 5 6
**/
cout << n;
}
Upvotes: 1
Views: 1727
Reputation: 1434
I had a similar problem and I used make_recursive_variant
then I used map<Key,Value>
for the variant instead of a Node class. And finally my node class became a utility function wrapping a std::map
reference. Such as:
typedef boost::make_recursive_variant<
std::map<std::string, boost::recursive_variant_>,
vector<int>,
vector<float>
>::type data_t;
// the printing visitor to ostream object
class PrintData : public boost::static_visitor<ostream&>
{
public:
ostream* os;
string prefix;
/// keep a pointer to the ostream and keep
/// a prefix string which will hold and "indent"
/// which is something like " " for every level
/// of recursion
PrintData(ostream& out_stream, const string& prefix_str="")
: os(&out_stream)
, prefix(prefix_str)
{}
/// recurse into the tree, adding indent characters to prefix
ostream& operator()(std::map<std::string, data_t>& tree) const
{
*os << endl;
for (auto& i : tree)
{
*os << prefix << i.first;
//you may want to change prefix and use new PrintData instead of *this
boost::apply_visitor(*this, i.second);
}
return *os;
}
/// actual data types that we want to print out
template <typename T>
ostream& operator()(const vector<T>& d) const
{
*os << " (vector<" << type_demangle(typeid(T).name()) << ">):";
for (T i : d)
{
*os << " " << i;
}
*os << endl;
return *os;
}
};
class Node
{
public:
//implicit ctor for data_t conversion
Node(data_t& data) : data(data) { }
//...
private:
data_t& data;
};
Edit: I forgot to mention that you dont need apply_visitor inside Node class anymore, but I am not sure about initializer_list
ctor. It may not work with data_t
types.
Upvotes: 2
Reputation: 163297
Declare the function, but don't define it inside the class. Instead, wait to define it until after you've defined PrintData
, at which point it will be a complete type.
class PrintData;
class Node
{
public:
...
ostream& apply_visitor(const PrintData& fn) const;
...
};
class PrintData : public boost::static_visitor<ostream&>
{ ... };
inline ostream& Node::apply_visitor(const PrintData& fn) const
{
...
}
Upvotes: 1