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]
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.
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.
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With