Search code examples
c++c++17variant

Problems with using std::variant on std::tuple


I'd like to use std::variant to process variant type but come up against some problems.

#include <string>
#include <vector>
#include <tuple>
#include <variant>

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;

void test_variant()
{
    using A = int;
    using B = double;
    using C = std::string;
    using var_t = std::variant<A, B, C>;

    using var_pair_t = std::tuple<var_t, var_t>;
    std::vector<var_pair_t> vec;

    for (var_pair_t const& var_pair : vec)
        std::visit(overloaded{
                    [](std::tuple<A, A> const& pair_A) {},
                    [](std::tuple<B, B> const& pair_B) {},
                    [](std::tuple<C, C> const& pair_C) {},
                    [](auto const& arg) {},
            }, var_pair);
}

On GCC: https://gcc.godbolt.org/z/p1ljQv
On VS2017 the compiler error:

C2672: 'function': no matching overloaded function found
C2783: 'declaration' : could not deduce template argument for 'identifier'

I want to handle the pair of the same type. What I am doing wrong? I suppose that pairs with different types would match auto const& arg, so all pair should match correctly.


Solution

  • That's just not how std::visit is specified:

    template <class Visitor, class... Variants>
    constexpr /*see below*/ visit(Visitor&& vis, Variants&&... vars);
    

    It takes a parameter pack of variants. It does not take a tuple of them, even though these are very similar things. And when you visit multiple variants, your function calls called with multiple arguments - not a tuple of them.

    To use the standard machinery, you want:

    std::visit(overloaded{
                [](A const&, A const&) {},                // <== unpack all of these
                [](B const&, B const&) {},
                [](C const&, C const&) {},
                [](auto const&, auto const&) {},
        }, std::get<0>(var_pair), std::get<1>(var_pair)); // <== unpack this too
    

    If you prefer to work in tuples, you can write your own version of visit that unpacks the variants and repacks the elements:

    template <typename F, typename Tuple>
    decltype(auto) visit_tuple(F f, Tuple t) {
        return std::apply([=](auto... vs){          // <== unpack the variants
            return std::visit([=](auto... elems){
                return f(std::tuple(elems...));     // <== repack the alternatives
            }, vs...);
        }, t);
    }
    

    Proper reference handling and forwarding is left as an exercise.