c++templatesboostoverridingstatic-visitor

boost::static_visitor as Map Value


I'm trying to create a lookup table for ints to a boost::static_visitor

using VariableValue = boost::variant<int, double, std::string>;

struct low_priority {};
struct high_priority : low_priority {};

struct Mul : boost::static_visitor < VariableValue>{
    template <typename T, typename U>
    auto operator() (high_priority, T a, U b) const -> decltype(VariableValue(a * b)) {
        return a * b;
    }

    template <typename T, typename U>
    VariableValue operator() (low_priority, T, U) const {
        throw std::runtime_error("Incompatible arguments");
    }

    template <typename T, typename U>
    VariableValue operator() (T a, U b) const {
        return (*this)(high_priority{}, a, b);
    }
};


const std::map < int, boost::static_visitor<VariableValue> > binopHelper = {
     {1, Mul{}}
};


However when I do the following:

std::cout << (VariableValue)boost::apply_visitor(binopHelper.at(1), (VariableValue)2, (VariableValue)4) << std::endl;

I get the error:

term does not evaluate to a function taking 2 arguments (compiling source file interpreter.cpp)

How can I make it so that static_visitor takes 2 arguments to match that of Mul?


Solution

  • You'd be slicing. You need dynamic allocation. The quickest way is to use type-erasure.

    The trick is to come up with a fixed statically known prototype. In this case, a binary function would be it, and you can add the apply_visitor dispatch to the Mul object:

    Live On Coliru

    #include <boost/variant.hpp>
    #include <functional>
    #include <iostream>
    #include <map>
    using VariableValue = boost::variant<int, double>;
    
    struct Mul : boost::static_visitor<VariableValue> {
        struct high_priority{};
        struct low_priority{};
    
        auto operator() (VariableValue const& a, VariableValue const& b) const {
            return boost::apply_visitor(*this, a, b);
        }
    
        template <typename T, typename U>
        auto operator() (high_priority, T a, U b) const -> decltype(VariableValue(a * b)) {
            return a * b;
        }
    
        template <typename T, typename U>
        VariableValue operator() (low_priority, T, U) const {
            throw std::runtime_error("Incompatible arguments");
        }
    
        template <typename T, typename U>
        VariableValue operator() (T a, U b) const {
            return (*this)(high_priority{}, a, b);
        }
    };
    
    const std::map < int, std::function<VariableValue(VariableValue const&, VariableValue const&)> > binopHelper = {
         {1, Mul{}}
    };
    
    int main() {
        VariableValue i(42), d(3.1415926);
    
        std::cout << binopHelper.at(1)(i, d) << "\n";
        std::cout << binopHelper.at(1)(d, i) << "\n";
    }
    

    Prints:

    131.947
    131.947
    

    EXTRA IDEAS

    It looks like you're implementing expression evaluation. You could do a lot simpler, by e.g. re-using the stadard library. I have a quite extensive demo here: https://github.com/sehe/qi-extended-parser-evaluator/blob/master/eval.h#L360 It was developed on [SO] in a chat discussion here: https://chat.stackoverflow.com/transcript/210289/2020/3/25

    Ask me anything if you want to know more.

    Specifically the code there shows how to handle type mismatches and implicit bool conversions where appropriate.