Search code examples
c++boostvariantboost-variant

How can I return a boost::variant made from a subset of the types contained in boost::varaint return type


I have 4 functions:

boost::variant<type1,type2,type3>processFile(const char* file){
     if(----expression that if true means I have to process as type 1----)
          return processType1(file);   //this just returns type1
     else if(----expression that if true means I have to process as either type 1 or type 2----)
           return processType23(file); //this returns boost::variant<type2,type3>. This also calls 2 more processing functions depending on type.
}

processType23 takes a script file which will determine which type gets sent back. I want to keep the determination of the type in this file. However I cannot return the boost::variant. I get the following error:

error: could not convert 'engine::fileManager::processLua(const char*)()' from 'boost::variant<engine::material, engine::shader>' to 'boost::variant<engine::material, engine::shader, unsigned int>'

What is the (proper) way to return the data?


Solution

  • You should use a visitor:

    Live On Coliru

    template <typename R, typename A> convert_variant(A const& arg) {
        return boost::apply_visitor([](auto const& v) -> R { return R{v}; }, arg);
    }
    

    Update

    In response to the observation made by @llonesmiz, you may wish to compile the conversion code even if some conversions might be illegal. In that case you will have to use some type traits to dinstinguish those cases and act accordingly:

    C++03 Demo

    Live On Coliru

    #include <boost/variant.hpp>
    #include <boost/type_traits.hpp>
    #include <iostream>
    
    template <typename R>
    struct convert_variant_visitor : boost::static_visitor<R> {
        struct bad_conversion : std::runtime_error {
            bad_conversion(std::string msg) : std::runtime_error(msg) {}
        };
    
        template <typename T>
            typename boost::enable_if_c<boost::is_convertible<T, R>::value, R>::type
            operator()(T const& v) const 
        {
            return R(v); // or just v
        }
    
        template <typename T>
            typename boost::enable_if_c<not boost::is_convertible<T, R>::value, R>::type
            operator()(T const& v) const 
        {
            throw bad_conversion(std::string("Cannot convert ") + typeid(v).name() + " to " + typeid(R).name());
            //throw bad_conversion("Not convertible to variant");
        }
    };
    
    template <typename R, typename A> R convert_variant(A const& arg) {
        return boost::apply_visitor(convert_variant_visitor<R>(), arg);
    }
    
    int main() {
        typedef boost::variant<std::string, int, double> V1;
        typedef boost::variant<int, double> V2;
        V1 input = 42;
        V2 output = convert_variant<V2>(input);
    
        std::cout << "input:  " << input  << " (which: " << input.which()  << ")\n";
        std::cout << "output: " << output << " (which: " << output.which() << ")\n";
    }
    

    Prints

    input:  42 (which: 1)
    output: 42 (which: 0)
    

    C++17 Demo

    Modern C++ features are making generic code like this much simpler:

    Live On Coliru

    template <typename R, typename A> R convert_variant(A const& arg) {
        return boost::apply_visitor([](auto const& v) -> R {
                if constexpr (std::is_convertible_v<decltype(v), R>)
                    return v;
                else
                    throw std::runtime_error("bad conversion");
            } , arg);
    }