0

I'm using boost-variant throughout my projects and I'm considering it to be one the most useful and versatile tools of boost.

But if it comes to complicated use of the visitor pattern with recursively nested variants the debugging is sometimes cumbersome.

Hence I decided to implement a reusable DebugVisitor, that might be added to my existing visitors. It should be easily added/removed to my existing visitors in case a defect occurs.

Easily removable means, that it should be addable to any existing Visitor class, instead of modifying the places, where instances of the Visitor are being used.

I tried to find a solution, which fits my requirements. The following code compiles, but unfortunately it does not print the message.

Does anyone knows why?

#include <iostream>
#include <boost/variant.hpp>
#include <functional>

template<typename V> // V must have the boost::static_visitor Interface
struct DebugVisitor : public V {
    template<typename U>
    typename V::result_type operator()(const U& u) const {
        std::cout << "Visiting Type:" << typeid(U).name() << " with Visitor: " << typeid(V).name() << std::endl;
        return V::operator()(u);
    }
};


struct AddVisitor : public DebugVisitor<boost::static_visitor<boost::variant<int, double>>> {
    template<typename U>
    result_type operator()(const U& u) const {
        return u + 1.;
    }
};

int main(int, char**) {
    boost::variant<double, int> number{ 3.2 };

    AddVisitor d;
    auto incr=number.apply_visitor(d);
    if (auto dValue = boost::get<double>(incr)) {
        std::cout << "Increment: " << dValue << std::endl;
    }
    return 0;
}
Aleph0
  • 5,816
  • 4
  • 29
  • 80

1 Answers1

1

The reason that you don't see debug output is because AddVisitor::operator() does not call DebugVisitor::operator(). If it did, you would have an error trying to resolve boost::static_visitor<>::operator(), which doesn't exist.

Option 1.

Just conditionally compile the debugging in the definition of AddVisitor

struct AddVisitor : public boost::static_visitor<boost::variant<int, double>> {
    template<typename U>
    result_type operator()(const U& u) const {
#ifdef DEBUG
        std::cout << "Visiting Type:" << typeid(U).name() << " with Visitor: " << typeid(AddVisitor).name() << std::endl;
#endif
        return u + 1.;
    }
};

Option 2.

Conditionally define the symbol AddVisitor to be wrapped by DebugVisitor

template<typename V> // V must have the boost::static_visitor Interface
struct DebugVisitor : public V {
    template<typename U>
    typename V::result_type operator()(const U& u) const {
        std::cout << "Visiting Type:" << typeid(U).name() << " with Visitor: " << typeid(V).name() << std::endl;
        return V::operator()(u);
    }
};

struct AddVisitorBase : public boost::static_visitor<boost::variant<int, double>> {
    template<typename U>
    result_type operator()(const U& u) const {
        return u + 1.;
    }
};

#ifdef DEBUG
using AddVisitor = DebugVisitor<AddVisitorBase>;
#else
using AddVisitor = AddVisitorBase;
#endif

Option 3.

A pair of CRTP bases

template<typename V> // V must have the boost::static_visitor Interface
struct DebugVisitor : public V {
    template<typename U>
    typename V::result_type operator()(const U& u) const {
        std::cout << "Visiting Type:" << typeid(U).name() << " with Visitor: " << typeid(V).name() << std::endl;
        return V::call(u);
    }
};

template<typename V> // V must have the boost::static_visitor Interface
struct NotDebugVisitor : public V {
    template<typename U>
    typename V::result_type operator()(const U& u) const {
        return V::call(u);
    }
};

struct AddVisitor : public boost::static_visitor<boost::variant<int, double>>, public 
#ifdef DEBUG
DebugVisitor<AddVisitor>
#else
NotDebugVisitor<AddVisitor>
#endif
{
    template<typename U>
    result_type call(const U& u) const {
        return u + 1.;
    }
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Thanks for your solution, but it unfortunately doesn't fit my requirement. Assume, that I'm using AddVisitor in scattered place all around my project. Now, something went wrong and I wanted to add Debugging facilities to all of my AddVisitor's. Then I have to replace it in every place. It would be nice, if I could somehow add the debugging facility right to AddVisitor. – Aleph0 Jul 27 '18 at 08:16
  • Nice. Is there some way to avoid having AddVisitorBase and AddVisitor? – Aleph0 Jul 27 '18 at 08:33
  • Not really. You can hide AddVisitorBase in a namespace – Caleth Jul 27 '18 at 08:34
  • A solution like `struct AddVisitor : public DebugVisitor>` is not possible? – Aleph0 Jul 27 '18 at 08:39
  • You'd still have to change `operator()`, so that you don't hide the one inherited from `DebugVisitor` – Caleth Jul 27 '18 at 08:42
  • Is there some way to avoid the hiding? I was not aware, that there is some hiding. – Aleph0 Jul 27 '18 at 08:55
  • When you have `Base::foo()` and `Derived::foo()`, and pass around `Derived`s, the only `foo` that get called is `Derived::foo()`. This is called "hiding" – Caleth Jul 27 '18 at 08:59
  • Very cool solution towards the end. I was also not aware of `call` either. Many thanks for your thorough solution. – Aleph0 Jul 27 '18 at 10:03
  • `call` is just a normal function. You can name it whatever you like – Caleth Jul 27 '18 at 10:04
  • Ouch. I thought it to be a substitute for operator(). Sorry. – Aleph0 Jul 27 '18 at 10:05
  • Ah. Now I fully understand the solution. I should have looked more carefully. – Aleph0 Jul 27 '18 at 10:06
  • Seems that I have to touch existing code and changing operator() to call()? – Aleph0 Jul 27 '18 at 10:08
  • If you want to use option 3, yes, you need to have the `DebugVisitor` define `operator()`, and `AddVisitor` have a method named for whatever `DebugVisitor` calls. The problem you started with is that an `AddVisitor::operator()` would mean that calling code would ignore `DebugVisitor::operator()`. – Caleth Jul 27 '18 at 10:13
  • However, I have presented these in order of most recommended to least recommended – Caleth Jul 27 '18 at 10:14