2017-09-07 17 views
7

Tôi tự hỏi làm cách nào để chuyển đổi loại trả về std::visit.Lượt truy cập biến thể và common_type

Ngữ cảnh như sau: Tôi có đối tượng biến thể và tôi muốn áp dụng (thông qua std::visit) các chức năng khác nhau tùy thuộc vào loại cơ bản của nó. Kết quả của mỗi chức năng có thể có một loại khác nhau, nhưng sau đó tôi muốn std :: truy cập để đóng gói nó lên trong một loại biến thể.

Pseudo-code:

tôi có:

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

Tôi muốn:

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> 

Bây giờ, theo cppreference, kiểu trả về của std :: chuyến thăm là "Giá trị được trả về bởi lời gọi được chọn của khách truy cập, được chuyển đổi thành loại phổ biến của tất cả các std có thể có :: biểu thức gọi " Nhưng phương tiện loại phổ biến nào không được chỉ định. Có phải là std::common_type không? Trong trường hợp này, nó không làm việc với 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] 

} 

Tôi nên làm gì để bày tỏ unpack này - xin thăm viếng - đóng gói lại mô hình?

Ghi chú:

1) Chuyên std::common_type<A(*)(Ts...),B(*)(Ts...)> sẽ không cắt nó. Điều này sẽ làm các trick nhưng dựa vào một chi tiết cụ thể std :: lib thực hiện. Ngoài ra, tính năng này không hoạt động đối với đa lượt truy cập.

2) Ví dụ tôi đưa ra thực sự giảm xuống mức tối thiểu, nhưng bạn phải hình dung cơ chế truy cập tôi muốn cung cấp ở phía thư viện và khách truy cập ở phía khách hàng và có thể tùy ý phức tạp: số không xác định và loại đối số, loại trả về không xác định. Thư viện chỉ nên cung cấp lượt truy cập và tập hợp các chuyên gia được xác định trước là std::common_type để sử dụng cho các loại trả lại truy cập. Vì vậy, ví dụ, xác định

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

và sau đó áp dụng std::visit-f không phải là một lựa chọn khả thi: từ phía thư viện, tôi không thể ấn định trước loại lambda mà không biết "đóng gói" kiểu trả về. [Vấn đề chính là tôi thấy không có cách nào hỏi ngôn ngữ cho std::common_type của một tập quá tải đặc biệt]

+0

@ Jarod42 Có bạn là đúng, đây không phải là vấn đề. Tôi đã không rõ ràng vì vậy tôi đã chỉnh sửa câu hỏi: phần thực sự mất tích thực sự là quá tải thiết lập loại trả lại phổ biến –

Trả lời

4

Bạn có thể tạo lớp visit của riêng bạn, một cái gì đó như:

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

+0

Hum thú vị, tôi không nghĩ về điều đó. Nó hoạt động cho một lần truy cập, nhưng tôi không thấy ngay bây giờ làm thế nào để mở rộng nó đến nhiều lần truy cập. –

+0

Để mở rộng thành nhiều, bạn cũng phải có common_type cho 'biến thể và C' thực sự không thể mở rộng được. Giải pháp của Yakk có khả năng mở rộng hơn, nhưng bạn vẫn có một số công việc (loại bỏ loại trùng lặp, ...). – Jarod42

+0

Tôi đang làm việc để tạo ra sản phẩm chéo của quá tải thiết lập đối số và nó khá phức tạp ... Một khi tôi đã làm xong, mở rộng giải pháp của bạn là tầm thường. Tôi sẽ đăng nó ở đây. –

2

Vấn đề chính của bạn là một thực tế rằng std::visit rõ ràng đòi hỏi tất cả các loại trả về của các lời gọi khác nhau được cung cấp bởi Khách truy cập có cùng loại và chuyên biệt std::common_type không có gì để khắc phục điều đó. Trình mô tả "Loại phổ biến" bạn đã lấy từ Tiêu chuẩn có nghĩa là thông tục, không phải là loại chữ.

Nói cách khác, các khách phải mang hình thức của

struct Visitor { 
    using some_type = /*...*/; 

    some_type operator()(A const& a); 
    some_type operator()(B const& b); 

}; 

May mắn thay, đây là một vấn đề mà giải quyết riêng của mình. Vì đã có loại phổ biến có thể được chỉ định từ loại hoán vị này trên giá trị được lưu trữ: số variant bạn đã mô tả ở địa điểm đầu tiên.

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}; 
    } 
}; 

Điều này sẽ biên dịch và mang lại hành vi bạn mong đợi.

+0

@ Jarod42 Đã sửa lỗi. Tôi không hiểu. – Xirema

+0

Tuy nhiên, vấn đề được đề cập trong lưu ý # 2 vẫn còn: trong trường hợp chung, tôi không muốn khách hàng của tôi trả lại biến thể và tôi không thể bao hàm các chức năng của mình vì tôi không biết loại trả về –

+1

@ Bérenger Bạn cần phải cụ thể hơn về lý do tại sao bạn không muốn trả về một đối tượng 'variant'. Bởi vì giải pháp cho vấn đề của bạn khác nhau tùy thuộc vào lý do chính xác mà một API trả về 'biến thể <...>' là xấu cho bạn. – Xirema

3

điều này cung cấp cho bạn kết quả của việc áp dụng F cho một nhóm các loại như một nhóm các loại.

Add ghi lại để từ biến thể, có thể lặp lại loại bỏ, một wrapper mà sẽ đưa Fvariant<Ts...> và tạo ra một F2 mà các cuộc gọi F và trả về cho biết biến thể, sau đó đi F2-visit, và chúng tôi cách hakf đó.

Một nửa còn lại là xử lý nhiều biến thể. Để có được điều đó, chúng ta cần phải lấy sản phẩm chéo của nhiều nhóm bó, có được kết quả gọi của tất cả chúng, và gói nó lên.

0

Giải pháp của tôi cho nhiều lần truy cập. Nhờ Jarod42 cho tôi thấy con đường với sự truy cập biến thể duy nhất.

Live Demo

Vấn đề chính là để tạo ra chéo sản phẩm của tất cả các cuộc gọi có thể để một bộ tình trạng quá tải. Câu trả lời này không giải quyết được vấn đề về việc chuyển đổi chung loại trả lại, tôi chỉ thực hiện một chuyên môn đặc biệt của std::common_type (Tôi nghĩ điều này là khó khăn cho phù hợp với nhu cầu của tôi, nhưng cảm thấy tự do để đóng góp!).

Xem các bài kiểm tra thời gian biên dịch ở cuối để hiểu từng hàm meta mẫu.

Hãy đề nghị đơn giản hóa (std::index_sequence ai?)

#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"; 
} 
Các vấn đề liên quan