Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variant visitation and common_type

Tags:

c++

c++17

variant

I am wondering how std::visit return type conversions are supposed to work.

The context is the following: I have a variant object and I want to apply (through std::visit) different functions depending on its underlying type. The result of each function may have a different type, but then I would like std::visit to pack it up in a variant type.

Pseudo-code:

I have:

variant<A,B> obj
f(A) -> A
f(B) -> B

I want:

if obj is of type A => apply f(A) => resA of type A => pack it in variant<A,B>
if obj is of type B => apply f(B) => resB of type B => pack it in variant<A,B>

Now, according to cppreference, the return type of std::visit is "The value returned by the selected invocation of the visitor, converted to the common type of all possible std::invoke expressions" But what common type means is not specified. Is it std::common_type ? In this case, it doesn't work with gcc 7.2:

#include <variant>
#include <iostream>
#include <type_traits>

struct A {
    int i;
};
struct B {
    int j;
};

// the standard allows to specialize std::common_type
namespace std {
    template<>
    struct common_type<A,B> {
        using type = std::variant<A,B>;
    };
    template<>
    struct common_type<B,A> {
        using type = std::variant<A,B>;
    };
}


struct Functor {
    auto
    operator()(A a) -> A {
        return {2*a.i};
    }
    auto
    operator()(B b) -> B {
        return {3*b.j};
    }
};


int main() {
    std::variant<A,B> var = A{42};

    auto res = std::visit( Functor() , var ); // error: invalid conversion from 'std::__success_type<B>::type (*)(Functor&&, std::variant<A, B>&) {aka B (*)(Functor&&, std::variant<A, B>&)}' to 'A (*)(Functor&&, std::variant<A, B>&)' [-fpermissive]

}

What should I do to express this unpack - apply visitation - repack pattern?

Notes:

1) Specializing std::common_type<A(*)(Ts...),B(*)(Ts...)> won't cut it. This would do the trick but rely on a particular std::lib implementation detail. Plus it doesn't work for multi-visitation.

2) The example I have given is really reduced to the bare minimum, but you have to imagine that the visitation mechanism I want to provide is on the library side, and the visitors are on the client side and can be arbitrary complicated: unknown number and types of arguments, unknown return types. The library should just provide visitation and a pre-defined set of std::common_type specializations to be used for visitation return types. So for instance, defining

auto f = [](auto x) -> variant<A,B> { return Functor()(x); };

and then applying std::visit to f is not a viable option: from the library side, I can't predefine this kind of lambda without knowing the "packed" return type. [The main problem is that I see no way of asking the language for the std::common_type of a particular overload set]

like image 405
Bérenger Avatar asked Sep 07 '17 19:09

Bérenger


People also ask

What does visit() do c++?

std::visit from C++17 is a powerful utility that allows you to call a function over a currently active type in std::variant . In this post, I'll show you how to leverage all capabilities of this handy function: the basics, applying on multiple variants, and passing additional parameters to the matching function.

What is std:: visit?

std::visit allows you to apply a visitor to a container of variants. The visitor must be a callable. A callable is something, which you can invoke. Typical callables are functions, function objects, or lambdas. I use lambdas in my example.

What is STD variant?

The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception).


1 Answers

You can create your own visit layer, something like:

template <typename Visitor, typename ... Ts>
decltype(auto) my_visit(Visitor&& vis, const std::variant<Ts...>& var)
{
    return std::visit([&](auto&& e)
        -> std::common_type_t<decltype(vis(std::declval<Ts>()))...>
        {
            return vis(e);
        }, var);
}

Demo

like image 153
Jarod42 Avatar answered Nov 02 '22 23:11

Jarod42