c++boostvariantstatic-visitor

boost::variant. boost::visitor to overloaded function


I have a problem with a variant value to an overloaded function. I want to call the overloaded function with an int or string depending on what is stored in the variant. This is how I want to do that, but i can't :

class X
{
    void foo(int i, int z) { /*use int i and z*/; }
    void foo(const std::string& s, int z) { /*use string s and z*/; }


    struct MyVisitor : public boost::static_visitor<int>  
    // !!! Here is the problem.
    // I can't return int or std::string,
    // so it's impossible to use template operator()
    {
        template<typename Data>
        const Data operator()(const Data data) const { return data; }
    };

public:

    /*somehow m_queue pushed ...*/

    void func_uses_variant(int z)
    {
        boost::variant<int, std::string> v = m_queue.pop();
        foo(boost::apply_visitor(MyVisitor(), v), z);
    }
private:
    SomeQueue m_queue;
}

Is it possible to write it using visitor or should I do something like this:

    void func_uses_variant(int z)
    {
        boost::variant<int, std::string> v = m_queue.pop();

        if (int* foo_arg = boost::get<int>(&v))
        {
            foo(*foo_arg, z);
        }
        else if (std::string* foo_arg = boost::get<std::string>(&v))
        {
            foo(*foo_arg, z);
        }
    }

I tried to to use variadics for MyVisitor but failed because of boost::static_visitor interface. Maybe there is a solution for that.

int z in function is just to show that there is not just boost::variant in the foo() parameters.


Solution

  • The whole point of visitors is to process each of the different types that could be held by the variant, in a form that doesn't involve get(), allows generics, and makes it easy to tell that you've handled all the options. If you can handle them all in one generic function, then you write a single template function in the visitor. If not, be more specific:

    struct foo_visitor : public boost::static_visitor<std::string>  
    {
        std::string operator()(int & i) const { return "this is int"; }
        std::string operator()(std::string &s) const { return "this is string"; }
    };
    

    This returns a common value from each possible type in the variant. Or you could use the visitor wrap the external function. Going back to the template function:

    struct foo_visitor : public boost::static_visitor<void>  
    {
        template<typename Data>
        void operator()(const Data &d) const { foo(d); }
    };
    

    EDIT[ It looks like this is provided by the library in the form of visitor_ptr, for single-argument functions. I think boost::visitor_ptr(foo) might be exactly equivalent. ]

    For the extra argument, you can add a constructor to the visitor and store an extra data member that gets passed into the wrapped function:

    struct foo_visitor : public boost::static_visitor<void>  
    {
        int z;
        foo_visitor(int z_param) : z(z_param) {};
    
        template<typename Data>
        void operator()(const Data &d) const { foo(d, z); }
    };
    
    {
        boost::variant<int, std::string> v;
        v = 5;
        int z = 0;
        boost::apply_visitor(foo_visitor(z), v);
    }
    

    edit from @Kabik:

    There is a way to do this using pattern matching with boost make_overloaded_function() and lambda.

    link to experiment match function - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0050r0.pdf

    boost::variant<int, std::string> v;
    v = 5;
    int z = 0;
    boost::apply_visitor(boost::bind<void>(boost::make_overloaded_function(
       [&](const int i) { foo(i, z); },
       [&](const std::string& str) { foo(str, z); }) _1
    ), v);