Baz
Baz

Reputation: 13125

Template for traversing a tree node using boost::variant

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

Answers (2)

Andy Prowl
Andy Prowl

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
    {
        ...
    }
};


SOLUTION 2: overload-based technique

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

Nim
Nim

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

Related Questions