Home > front end >  No user defined conversion when using standard variants and visitor pattern
No user defined conversion when using standard variants and visitor pattern

Time:12-04

Could you please help me figure out why is this not working i.e. refering to the comment in the code //I need to do this but I can't. I thought this the goal!? I have no idea why this is not working, it's inspired by examples I have seen online.

#include <variant>
#include <iostream>

template<typename... args>
class Visitor //: public boost_base_visitor<double>...
{
public:
    virtual ~Visitor() = default;

    virtual double visit(typename std::variant<args...> visitable)
    {
        auto op = [this](typename std::variant<args...> visitable) -> double { return this->apply(visitable); };
        return std::visit(std::ref(op), visitable);
    }

    virtual double apply(typename std::variant<args...> visitable) = 0;

    Visitor() = default;
};

class SubVisitor : public Visitor<std::string, double>
{
public:
    virtual ~SubVisitor() = default;
    SubVisitor() : Visitor<std::string, double>() {};
    
    virtual double apply(std::variant<std::string, double> visitable) override
    {
        //return process(visitable); //I need to do this but I can't. I thought this the goal!
        return process(std::get<std::string>(visitable)); //I DON'T KNOW IF THIS IS REALLY A STRING??
    };

    virtual double process(std::string visitable)
    {
        std::cout << "STRING HANDLED" << std::endl;
        return 0.0;
    }

    virtual double process(double visitable)
    {
        std::cout << "DOUBLE HANDLED" << std::endl;
        return 1.0;
    }
};

int main(int argc, char* argv[])
{
    SubVisitor v;
    v.apply("dd");
    //v.apply(1.0); //This will fail as we only handle string?? What is the purpose of variant then?
    return 1;
}

I am getting error when uncommenting the process function above:

Error C2664: 'double SubVisitor::process(std::string)': cannot convert argument 1 from 'std::variantstd::string,double' to 'std::string'

CodePudding user response:

you can use std:visit to operate on the variant

class SubVisitor{
    virtual double apply(std::variant<std::string, double> visitable) override
    {
        // std::visit expect `operator()`, not `process`
        // so wrap `this` inside a lambda here
        return std::visit(            
            [this](auto&& v){return process(v);},
            visitable
        );
    };
}

or if you want, you can also check the type manually

class SubVisitor{
    virtual double apply(std::variant<std::string, double> visitable) override
    {
        if(auto* s = std::get_if<std::string>(&visitable))
            return process(*s);
        
        else if(auto* d = std::get_if<double>(&visitable))
            return process(*d);

        throw std::bad_variant_access();
    }
};

CodePudding user response:

in addition to the answer, imo the base class should be something like

template<typename... args>
class Visitor{
public:
    Visitor() = default;
    virtual ~Visitor() = default;

    // this should be non-virtual
    double visit(std::variant<args...> visitable)
    {
        // dispatch via the customization point `this->apply`
        return this->apply(visitable);
    }
    virtual double apply(std::variant<args...> visitable) = 0;
};
  • Related