我想知道std::visit
返回类型转换应该如何工作。
上下文如下:
我有一个变体对象,我想根据其基础类型应用(通过std::visit
)不同的函数。每个函数的结果可能有不同的类型,但我希望std :: visit以变种类型打包它。
的伪代码:
我有:
variant<A,B> obj
f(A) -> A
f(B) -> B
我想:
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>
现在,根据cppreference,std :: visit的返回类型是“所选访问者调用返回的值,转换为所有可能的std :: invoke表达式的公共类型”
但是没有指定常见类型的含义。是std::common_type
吗?在这种情况下,它不适用于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]
}
我该怎么做来表达这种解压 - 应用访问 - 重新包装模式?
备注:
1)专业化std::common_type<A(*)(Ts...),B(*)(Ts...)>
不会削减它。这可以解决问题,但依赖于特定的std :: lib实现细节。
此外,它不适用于多次访问。
2)我给出的例子实际上已经减少到最低限度,但你必须想象我想要提供的访问机制是在库侧,访问者是在客户端,可以是任意复杂的:未知数量和类型的参数,未知的返回类型。该库应该只提供访问和一组预定义的std::common_type
专业化,用于访问返回类型。例如,定义
auto f = [](auto x) -> variant<A,B> { return Functor()(x); };
然后将std::visit
应用于f
不是一个可行的选择:从库方面来说,我不能在不知道“打包”返回类型的情况下预定义这种lambda。 [主要问题是我认为没有办法询问特定过载集的std::common_type
的语言]
答案 0 :(得分:4)
您可以创建自己的visit
图层,例如:
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);
}
答案 1 :(得分:3)
template<class...>struct types{using type=types;};
template<class F, class Types>
struct results;
template<class F, class...Ts>
struct results<F, types<Ts...>>:
types<std::invoke_result_t<F,Ts>...>
{};
这为您提供了将F
作为一组类型应用于一组类型的结果。
添加转录来自变体,可能是重复删除,使用F
和variant<Ts...>
的包装并创建调用F2
的{{1}}并返回所述变体,然后将F
传递给F2
,我们很开心。
另一半是处理多种变体。为此,我们需要获取多个类型包的交叉产品,获取所有类型的调用结果,并将其捆绑。
答案 2 :(得分:2)
您的主要问题是std::visit
明确要求访问者提供的各种调用的所有返回类型属于同一类型,并且专门std::common_type
无法解决此问题。从标准中提取的“公共类型”描述符通常是通俗的,而不是文字类型。
换句话说,访客必须采用
的形式struct Visitor {
using some_type = /*...*/;
some_type operator()(A const& a);
some_type operator()(B const& b);
};
幸运的是,这是一个解决问题的问题。因为已经存在一种可以通过存储值的这种排列分配的公共类型:首先描述的variant
。
struct Functor {
std::variant<A,B> operator()(A const& a) const {
return A{2*a.i};
}
std::variant<A,B> operator()(B const& b) const {
return B{3*b.j};
}
};
这应该编译并产生你期望的行为。
答案 3 :(得分:0)
我的多重访问解决方案。感谢Jarod42向我展示单一变体访问的方式。
主要问题是生成对重载集的所有可能调用的交叉积。
这个答案没有解决返回类型的泛型转换问题,我只是对std::common_type
进行了临时专业化(我认为这是符合我需要的,但随时可以做出贡献!)。
请参阅最后的编译时测试以了解每个模板元函数。
随意建议简化(std::index_sequence
任何人?)
#include <variant>
#include <iostream>
#include <type_traits>
// ========= Library code ========= //
// --- Operations on types --- //
template<class... Ts>
struct Types; // used to "box" types together
// Lisp-like terminology
template<class Head, class Tail>
struct Cons_types;
template<class Head, class... Ts>
struct Cons_types<Head,Types<Ts...>> {
using type = Types<Head,Ts...>;
};
template<class... _Types>
struct Cat_types;
template<class _Types, class... Other_types>
struct Cat_types<_Types,Other_types...> {
using type = typename Cat_types<_Types, typename Cat_types<Other_types...>::type>::type;
};
template<class... T0s, class... T1s>
struct Cat_types< Types<T0s...> , Types<T1s...> > {
using type = Types< T0s..., T1s... >;
};
template<class... T0s>
struct Cat_types< Types<T0s...> > {
using type = Types< T0s... >;
};
template<class Head, class Types_of_types>
struct Cons_each_types;
template<class Head, class... Ts>
struct Cons_each_types<Head,Types<Ts...>> {
using type = Types< typename Cons_types<Head,Ts>::type... >;
};
template<class Head>
struct Cons_each_types<Head,Types<>> {
using type = Types< Types<Head> >;
};
template<class _Types>
struct Cross_product;
template<class... Ts, class... Other_types>
struct Cross_product< Types< Types<Ts...>, Other_types... > > {
using type = typename Cat_types< typename Cons_each_types<Ts,typename Cross_product<Types<Other_types...>>::type>::type...>::type;
};
template<>
struct Cross_product<Types<>> {
using type = Types<>;
};
// --- Operations on return types --- //
template<class Func, class _Types>
struct Common_return_type;
template<class Func, class... Args0, class... Other_types>
struct Common_return_type<Func, Types< Types<Args0...>, Other_types... >> {
using type =
std::common_type_t<
std::result_of_t<Func(Args0...)>, // C++14, to be replaced by std::invoke_result_t in C++17
typename Common_return_type<Func,Types<Other_types...>>::type
>;
};
template<class Func, class... Args0>
struct Common_return_type<Func, Types< Types<Args0...> >> {
using type = std::result_of_t<Func(Args0...)>;
};
// --- Operations on variants --- //
template<class... Vars>
struct Vars_to_types;
template<class... Ts, class... Vars>
struct Vars_to_types<std::variant<Ts...>,Vars...> {
using type = typename Cons_types< Types<Ts...> , typename Vars_to_types<Vars...>::type >::type;
};
template<>
struct Vars_to_types<> {
using type = Types<>;
};
template<class Func, class... Vars>
// requires Func is callable
// requires Args are std::variants
struct Common_return_type_of_variant_args {
using Variant_args_types = typename Vars_to_types<Vars...>::type;
using All_args_possibilities = typename Cross_product<Variant_args_types>::type;
using type = typename Common_return_type<Func,All_args_possibilities>::type;
};
template <typename Func, class... Args>
// requires Args are std::variants
decltype(auto)
visit_ext(Func&& f, Args... args) {
using Res_type = typename Common_return_type_of_variant_args<Func,Args...>::type;
return std::visit(
[&](auto&&... e)
-> Res_type
{
return f(std::forward<decltype(e)>(e)...);
},
std::forward<Args>(args)...);
}
// ========= Application code ========= //
struct A {
int i;
};
struct B {
int j;
};
// This part is not generic but is enough
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>;
};
template<>
struct common_type<A,std::variant<A,B>> {
using type = std::variant<A,B>;
};
template<>
struct common_type<std::variant<A,B>,A> {
using type = std::variant<A,B>;
};
template<>
struct common_type<B,std::variant<A,B>> {
using type = std::variant<A,B>;
};
template<>
struct common_type<std::variant<A,B>,B> {
using type = std::variant<A,B>;
};
}
struct Functor {
auto
operator()(A a0,A a1) -> A {
return {a0.i+2*a1.i};
}
auto
operator()(A a0,B b1) -> A {
return {3*a0.i+4*b1.j};
}
auto
operator()(B b0,A a1) -> B {
return {5*b0.j+6*a1.i};
}
auto
operator()(B b0,B b1) -> B {
return {7*b0.j+8*b1.j};
}
};
// ========= Tests and final visit call ========= //
int main() {
std::variant<A,B> var0;
std::variant<A,B> var1;
using Variant_args_types = typename Vars_to_types<decltype(var0),decltype(var1)>::type;
static_assert(
std::is_same_v<
Types< Types<A,B>, Types<A,B> >,
Variant_args_types
>
);
using Cons_A_Nothing = typename Cons_each_types<A, Types<> >::type;
static_assert(
std::is_same_v<
Types< Types<A> >,
Cons_A_Nothing
>
);
using Cons_A_AB = typename Cons_each_types<A, Types<Types<A>,Types<B>> >::type;
using Cons_B_AB = typename Cons_each_types<B, Types<Types<A>,Types<B>> >::type;
static_assert(
std::is_same_v<
Types< Types<A,A>, Types<A,B> >,
Cons_A_AB
>
);
using Cat_types_A = typename Cat_types<Cons_A_Nothing>::type;
static_assert(
std::is_same_v<
Types< Types<A> >,
Cat_types_A
>
);
using Cat_types_AA_AB_BA_BB = typename Cat_types<Cons_A_AB,Cons_B_AB>::type;
static_assert(
std::is_same_v<
Types< Types<A,A>, Types<A,B>, Types<B,A>, Types<B,B> >,
Cat_types_AA_AB_BA_BB
>
);
using Depth_x1_1_cross_product = typename Cross_product<Types<Types<A>>>::type;
static_assert(
std::is_same_v<
Types< Types<A> >,
Depth_x1_1_cross_product
>
);
using Depth_x2_1_1_cross_product = typename Cross_product<Types<Types<A>,Types<B>>>::type;
static_assert(
std::is_same_v<
Types< Types<A,B> >,
Depth_x2_1_1_cross_product
>
);
using All_args_possibilities = typename Cross_product<Variant_args_types>::type;
static_assert(
std::is_same_v<
Types< Types<A,A>, Types<A,B>, Types<B,A>, Types<B,B> >,
All_args_possibilities
>
);
using Functor_AorB_AorB_common_return_type = typename Common_return_type<Functor,All_args_possibilities>::type;
static_assert(
std::is_same_v<
std::variant<A,B>,
Functor_AorB_AorB_common_return_type
>
);
using Functor_varAB_varAB_common_return_type = typename Common_return_type_of_variant_args<Functor,decltype(var0),decltype(var1)>::type;
static_assert(
std::is_same_v<
std::variant<A,B>,
Functor_varAB_varAB_common_return_type
>
);
var0 = A{42};
var1 = A{43};
auto res0 = visit_ext(Functor(), var0,var1);
std::cout << "res0 = " << std::get<A>(res0).i << "\n";
var0 = A{42};
var1 = B{43};
auto res1 = visit_ext(Functor(), var0,var1);
std::cout << "res1 = " << std::get<A>(res1).i << "\n";
var0 = B{42};
var1 = A{43};
auto res2 = visit_ext(Functor(), var0,var1);
std::cout << "res2 = " << std::get<B>(res2).j << "\n";
var0 = B{42};
var1 = B{43};
auto res3 = visit_ext(Functor(), var0,var1);
std::cout << "res3 = " << std::get<B>(res3).j << "\n";
}