变体访问和common_type

时间:2017-09-07 19:26:46

标签: c++ c++17 variant

我想知道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的语言]

4 个答案:

答案 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);
}

Demo

答案 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作为一组类型应用于一组类型的结果。

添加转录来自变体,可能是重复删除,使用Fvariant<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向我展示单一变体访问的方式。

Live Demo

主要问题是生成对重载集的所有可能调用的交叉积。 这个答案没有解决返回类型的泛型转换问题,我只是对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";
}