Search code examples
c++variantboost-variant

Variant visitor with different return types


Consider the following piece of code that uses boost::variant (but should apply perfectly well to std::variant as well).

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

int main()
{
    boost::variant<std::vector<int>, std::vector<double> > vr 
        = std::vector<int>(5, 5);;

    // OK, no problem.
    boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr);

    // ERROR: return types must not differ.
    //boost::apply_visitor([](auto a) { return a.begin(); }, vr);
}

Here, we have variant that eats up standard vectors of different types (e.g., int and double in this example), and we'd like to have a visitor that returns objects of different types (in this case, iterators to the beginning of the underlying container). However, this won't compile as obviously std::vector<int>::iterator is not the same as std::vector<double>::iterator. Is there a neat way of essentially achieving this, possibly through an extra layer of indirection?


Solution

  • You can return a different variant

    #include <iostream>
    #include <vector>
    #include <boost/variant.hpp>
    
    int main()
    {
        boost::variant<std::vector<int>, std::vector<double> > vr 
            = std::vector<int>(5, 5);
        using iter_variant = boost::variant<std::vector<int>::iterator, std::vector<double>::iterator >;
    
        using value_variant = boost::variant<int, double>;
    
        // OK, no problem.
        boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr);
    
        // Also OK
        boost::apply_visitor([](auto a) -> iter_variant { return a.begin(); }, vr);
    
        // Also OK
        boost::apply_visitor([](auto a) -> value_variant { return a[0]; }, vr);
    }
    

    See it live

    Given a generic lambda and a variant, you can get an appropriate return type.

    template<typename Func, typename Variant>
    struct visitor_result;
    
    template<typename Func, typename ... Ts>
    struct visitor_result<Func, boost::variant<Ts...>>
    {
        using type = boost::variant<decltype(std::declval<Func>()(std::declval<Ts>()))...>;
    };
    
    template<typename Func, typename Variant>
    using visitor_result_t = typename visitor_result<Func, Variant>::type;
    
    template<typename Func, typename Variant>
    visitor_result_t<Func, Variant> generic_visit(Func func, Variant variant)
    {
        return boost::apply_visitor([&](auto a) -> visitor_result_t<Func, Variant> { return func(a); }, variant);
    }
    

    See it live