Reputation: 13125
Here is my design for traversal of a node tree:
struct Leaf1{};
struct Leaf2{};
struct Leaf3{};
struct Leaf4{};
struct Leaf5{};
typedef boost::variant< Leaf4, Leaf5 > Node3;
typedef boost::variant< Leaf2, Leaf3, Node3> Node2;
typedef boost::variant< Node2, Leaf1 > Node1;
class NodeVisitor: public boost::static_visitor<void>
{
public:
template<class Node>
void operator()(const Node& e) const
{
boost::apply_visitor( *this, e );
}
void operator()(const Leaf1& e) const{}
void operator()(const Leaf2& e) const{}
void operator()(const Leaf3& e) const{}
void operator()(const Leaf4& e) const{}
void operator()(const Leaf5& e) const{}
};
So I recursively visit the nodes until I arrive at a leaf. The problem above is that I must add a stub for operater() for each leaf. You can see that I have five such stubs above but have many more in practice. Can you suggest a way of templating this stub?
Upvotes: 3
Views: 641
Reputation: 126422
SOLUTION 1: SFINAE-based technique
This solution is based on the fact that failing to substitute template parameters during the instantiation of a template does not cause a compilation error (Substitution Failure Is Not An Error): instead, that template is simply disregarded for overload resolution. Thus, with some trick you can select which overloads of a certain function template shall be made visible depending on the template arguments provided at instantiation time.
When this technique is used, it is important to make sure that the discriminating conditions which decide the visibility of each overload are mutually exclusive, or ambiguity may arise.
To begin with, you need to define some trait metafunction that helps you determine whether a certain class is a leaf:
// Primary template
template<typename T> struct is_leaf<T> { static const bool value = false; };
// Specializations...
template<> struct is_leaf<Leaf1> { static const bool value = true; };
template<> struct is_leaf<Leaf2> { static const bool value = true; };
...
Then, you can use std::enable_if
(or boost::enable_if
if you are working with C++98) to select which overload of the call operator should be made visible:
class NodeVisitor: public boost::static_visitor<void>
{
public:
// Based on the fact that boost::variant<> defines a type list called
// "types", but any other way of detecting whether we are dealing with
// a variant is OK
template<typename Node>
typename std::enable_if<
!is_same<typename Node::types, void>::value
>::type
operator()(const Node& e) const
{
boost::apply_visitor( *this, e );
}
// Based on the fact that leaf classes define a static constant value
// called "isLeaf", but any other way of detecting whether we are dealing
// with a leaf is OK
template<typename Leaf>
typename std::enable_if<is_leaf<Leaf>::value>::type
operator()(const Leaf& e) const
{
...
}
};
If you are working on C++98 and do not want to use boost::enable_if
as a replacement for std::enable_if
, an alternative approach consists in exploiting overload resolution and an unused argument for discriminating between two overloads of a helper function. First of all, you define two dummy classes:
struct true_type { };
struct false_type { };
Then, you create your is_leaf<>
metafunction again, properly specializing it for leaf classes:
// Primary template
template<typename T> struct is_leaf<T> { typedef false_type type; };
// Specializations...
template<> struct is_leaf<Leaf1> { typedef true_type type; };
template<> struct is_leaf<Leaf2> { typedef true_type type; };
...
Finally, you create an instance of one of those dummy types to choose the proper overload of a helper function process()
:
class NodeVisitor: public boost::static_visitor<void>
{
public:
template<typename T>
void operator()(const T& e) const
{
typedef typename is_leaf<T>::type helper;
process(e, helper());
}
template<typename Node>
void process(const Node& e, false_type) const
{
boost::apply_visitor(*this, e);
}
template<typename Leaf>
void process(const Leaf& e, true_type) const
{
...
}
};
Upvotes: 3
Reputation: 33655
Use an additional level of indirection for the LeafN
class, something like:
template <typename LeafType>
struct LeafHolder
{
// has real instance of leaf..
};
Then redefine your variant type
typedef boost::variant< LeafHolder<Leaf4>, LeafHolder<Leaf5> > Node3;
typedef boost::variant< LeafHolder<Leaf2>, LeafHolder<Leaf3>, Node3> Node2;
typedef boost::variant< Node2, LeafHolder<Leaf1 > Node1;
Now your visitor can become:
class NodeVisitor: public boost::static_visitor<void>
{
public:
template<class Node>
void operator()(const Node& e) const
{
boost::apply_visitor( *this, e );
}
// single function to handle all leaves...
template <typename LeafType>
void operator()(const LeafHolder<LeafType>& e) const{}
};
Upvotes: 2