4

I'm using boost::variant quite often in my projects. My colleagues now came up with the idea to pass around instances of specific boost::static_visitor<int> in order to customize the type of visitation. She had some code like the following:

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

typedef boost::variant<int, std::string> TVar;

struct Visitor1 : public boost::static_visitor<int> {
    template<typename T> 
    result_type operator()(const T&) {
        return 42;
    }
};

struct Visitor2 : public boost::static_visitor<int> {
    template<typename T>
    result_type operator()(const T&) {
        return 21;
    }
};

 int adder(const boost::static_visitor<int>& visitor, const TVar& visitable) {
     return visitable.apply_visitor(visitor) + 1;
 }

int main(int argc, char **args) {
    Visitor1 v1;
    Visitor2 v2;
    TVar x;

    std::cout << adder(v1, x) << std::endl;
    std::cout << adder(v2, x) << std::endl;
}

It looks perfectly sound to me, but it doesn't compile. My compiler says that expression will not yield a function that takes one 1 argument somewhere insider .

What are we doing wrong?

Aleph0
  • 5,816
  • 4
  • 29
  • 80

3 Answers3

4

There's an overload for apply_visitor that takes only the visitor. This returns a partially applied function object that suits your needs, I guess:

This overload returns a

Class template apply_visitor_delayed_t boost::apply_visitor_delayed_t — Adapts a visitor for use as a function object.

Personally, I like to add an overload to the visitor object like this:

struct MyVisitor {
    typedef void result_type;

    template <typename... Ts>
    result_type operator()(boost::variant<Ts...> const& v) const {
         return boost::apply_visitor(*this, v);
    }
    // optionally repeat for non-const `v`

    // normal variant handling overloads
};

That way, you can simply use the visitor object as the function oject.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Very interesting. I was not even aware of this call. But still I'm unsure how to use it. Assume I have `auto y=boost::apply_visitor(v1)`. How can I pass `y ` to my `adder` function? – Aleph0 Apr 28 '17 at 09:44
  • 1
    You pass it like you would anywhere you pass a calleable object (either deduce the argument type with template type argument or erase that type using `std::function<>` or similar) – sehe Apr 28 '17 at 09:47
  • Many thanks. I think that Angew already showed my how to do it. :-) – Aleph0 Apr 28 '17 at 09:47
  • Indeed. That's part of his approach as well – sehe Apr 28 '17 at 09:48
3

static_visitor is not a polymorphic base class, and cannot be treated as such. It's in the very name: it's a static (statically typed) visitor. You need the static type of the visitor to be the one you want to invoke. Note that this is unavoidable, because member function templates cannot be virtual. In your case, this means adder will have to become a function template, templated by the visitor type:

template <class Visitor>
int adder(const Visitor& visitor, const TVar& visitable) {
  return visitable.apply_visitor(visitor) + 1;
}

Use in main will then remain the same.

If you cannot afford to make adder a template, you can use apply_visitor mentioned by @sehe's answer in combination with std::function (or boost::function if you cannot use C++11):

int adder(std::function<int(const TVar&)> visitorApplier, const TVar& visitable)
{
  return visitorApplier(visitable);
}

int main(int argc, char **args) {
    Visitor1 v1;
    Visitor2 v2;
    TVar x;

    std::cout << adder(boost::apply_visitor(v1), x) << std::endl;
    std::cout << adder(boost::apply_visitor(v2), x) << std::endl;
}

[Live example]

Technically, the visitor itself is an appropriate callable object already, so it should work even without explicit use of apply_visitor:

int main(int argc, char **args) {
    Visitor1 v1;
    Visitor2 v2;
    TVar x;

    std::cout << adder(v1, x) << std::endl;
    std::cout << adder(v2, x) << std::endl;
}
Community
  • 1
  • 1
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Seems to be a very cool solution. Unfortunately, it is not compiling. Do I have a problem with my visual studio compiler? It should support C++11. – Aleph0 Apr 28 '17 at 09:51
  • It says that converting from `boost::apply_visitor_delayed_t` to `const std::function &` is not possible. I made the passed variable `visitorApplier` a const reference. – Aleph0 Apr 28 '17 at 09:55
  • Just found out, that you can replace `adder(boost::apply_visitor(v1), x)` by `adder(v1, x)`. So you can even shorten your solution. – Aleph0 Apr 28 '17 at 10:03
3

Firstly, you should mark your visitors' operator() overloads as const:

struct Visitor1 : public boost::static_visitor<int> {
    template<typename T> 
    result_type operator()(const T&) const {
        return 42;
    }
};

struct Visitor2 : public boost::static_visitor<int> {
    template<typename T>
    result_type operator()(const T&) const {
        return 21;
    }
};

Then, you need to change your adder to accept a template parameter instead of boost::static_visitor - it is your Visitor1 type that contains the operator() overload with the logic you desire. You need the "correct type" for the visitation to take place.

template <typename T>
int adder(const T& visitor, const TVar& visitable) {
    return visitable.apply_visitor(visitor) + 1;
}

live wandbox example

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416