2017-06-02 17 views
5

Được cảnh báo: Có rất nhiều thông tin cơ bản ở đây trước khi chúng tôi nhận được câu hỏi thực sự.Cách tốt nhất để thực hiện điều này "về lỗi, ném" gọi lại là gì?

Tôi có một C++ hệ thống phân cấp lớp khá rộng (đại diện cho một cái gì đó giống như biểu hiện của các loại khác nhau):

class BaseValue { virtual ~BaseValue(); }; 
class IntValue final : public BaseValue { int get() const; }; 
class DoubleValue final : public BaseValue { double get() const; }; 
class StringValue final : public BaseValue { std::string get() const; }; 

Và ở phía bên kia, tôi có một cách để ép buộc đầu vào của người dùng đến một loại dự kiến:

class UserInput { template<class T> get_as() const; }; 

Vì vậy, một cách để viết đối sánh - "đầu vào của người dùng có bằng giá trị BaseValue này không?" - sẽ như sau:

class BaseValue { virtual bool is_equal(UserInput) const; }; 
class IntValue : public BaseValue { 
    int get() const; 
    bool is_equal(UserInput u) const override { 
     return u.get_as<int>() == get(); 
    } 
}; 
// and so on, with overrides for each child class... 
bool does_equal(BaseValue *bp, UserInput u) { 
    return bp->is_equal(u); 
} 

Tuy nhiên, điều này không quy mô theo hướng "chiều rộng của phân cấp" hoặc theo hướng "số hoạt động". Ví dụ, nếu tôi muốn thêm bool does_be_greater(BaseValue*, UserInput), điều đó đòi hỏi một phương thức ảo toàn bộ với các triển khai N nằm rải rác trên hệ thống phân cấp. Vì vậy, tôi quyết định đi tuyến đường này thay vì:

bool does_equal(BaseValue *bp, UserInput u) { 
    if (typeid(*bp) == typeid(IntValue)) { 
     return static_cast<IntValue*>(bp)->get() == u.get_as<int>(); 
    } else if (typeid(*bp) == typeid(DoubleValue)) { 
     return static_cast<DoubleValue*>(bp)->get() == u.get_as<double>(); 
    ... 
    } else { 
     throw Oops(); 
    } 
} 

Trong thực tế, tôi có thể làm một số lập trình meta và sụp đổ mà xuống thành một chức năng duy nhất visit tham gia một lambda generic:

bool does_equal(BaseValue *bp, UserInput u) { 
    my::visit<IntValue, DoubleValue, StringValue>(*bp, [&](const auto& dp){ 
     using T = std::decay_t<decltype(dp.get())>; 
     return dp.get() == u.get_as<T>(); 
    }); 
} 

my::visit được thực hiện như một mẫu chức năng "đệ quy": my::visit<A,B,C> chỉ cần kiểm tra typeid chống lại A, gọi lambda nếu có, và gọi my::visit<B,C> nếu không. Ở dưới cùng của ngăn xếp cuộc gọi, my::visit<C> kiểm tra typeid chống lại C, gọi lambda nếu có, và ném Oops() nếu không.

Được rồi, bây giờ cho câu hỏi thực tế của tôi!

Sự cố với my::visit là hành vi trên báo lỗi "ném Oops()" được mã hóa cứng. Tôi thực sự muốn có hành vi lỗi do người dùng chỉ định, như sau:

bool does_be_greater(BaseValue *bp, UserInput u) { 
    my::visit<IntValue, DoubleValue, StringValue>(*bp, [&](const auto& dp){ 
     using T = std::decay_t<decltype(dp.get())>; 
     return dp.get() > u.get_as<T>(); 
    }, [](){ 
     throw Oops(); 
    }); 
} 

Vấn đề tôi gặp phải là khi tôi làm điều đó, tôi không thể tìm ra cách triển khai lớp cơ sở theo cách mà trình biên dịch sẽ đóng lại về các kiểu trả về không khớp hoặc rơi ra khỏi đầu của một hàm!Dưới đây là phiên bản mà không có một on_error callback:

template<class Base, class F> 
struct visit_impl { 
    template<class DerivedClass> 
    static auto call(Base&& base, const F& f) { 
     if (typeid(base) == typeid(DerivedClass)) { 
      using Derived = match_cvref_t<Base, DerivedClass>; 
      return f(std::forward<Derived>(static_cast<Derived&&>(base))); 
     } else { 
      throw Oops(); 
     } 
    } 

    template<class DerivedClass, class R, class... Est> 
    static auto call(Base&& base, const F& f) { 
    [...snip...] 
}; 

template<class... Ds, class B, class F> 
auto visit(B&& base, const F& f) { 
    return visit_impl<B, F>::template call<Ds...>(std::forward<B>(base), f); 
} 

Và đây là những gì tôi thực sự muốn có:

template<class Base, class F, class E> 
struct visit_impl { 
    template<class DerivedClass> 
    static auto call(Base&& base, const F& f, const E& on_error) { 
     if (typeid(base) == typeid(DerivedClass)) { 
      using Derived = match_cvref_t<Base, DerivedClass>; 
      return f(std::forward<Derived>(static_cast<Derived&&>(base))); 
     } else { 
      return on_error(); 
     } 
    } 

    template<class DerivedClass, class R, class... Est> 
    static auto call(Base&& base, const F& f, const E& on_error) { 
    [...snip...] 
}; 

template<class... Ds, class B, class F, class E> 
auto visit(B&& base, const F& f, const E& on_error) { 
    return visit_impl<B, F>::template call<Ds...>(std::forward<B>(base), f, on_error); 
} 

Đó là, tôi muốn để có thể xử lý cả hai trường hợp:

template<class... Ds, class B, class F> 
auto visit_or_throw(B&& base, const F& f) { 
    return visit<Ds...>(std::forward<B>(base), f, []{ 
     throw std::bad_cast(); 
    }); 
} 

template<class... Ds, class B> 
auto is_any_of(B&& base) { 
    return visit<Ds...>(std::forward<B>(base), 
     []{ return true; }, []{ return false; }); 
} 

Vì vậy, tôi đoán một cách để làm điều đó sẽ viết một số chuyên môn của vỏ cơ sở:

  • khi is_void_v<decltype(on_error())>, sử dụng {on_error(); throw Dummy();} để bịt miệng các trình biên dịch cảnh báo

  • khi is_same_v<decltype(on_error()), decltype(f(Derived{}))>, sử dụng {return on_error();}

  • khác, tĩnh khẳng định

Nhưng tôi cảm thấy như tôi đang thiếu một số cách tiếp cận đơn giản hơn. Có ai nhìn thấy nó không?

+1

A [MCVE] tái tạo các cảnh báo trình biên dịch mà bạn muốn im lặng sẽ được tốt đẹp. –

+0

Toán tử dấu phẩy luôn sẵn sàng bị lạm dụng: http://coliru.stacked-crooked.com/a/78f96318349b604b –

Trả lời

2

Tôi đoán một cách để làm điều đó sẽ được viết một số chuyên ngành của các trường hợp cơ sở

Thay vì làm điều đó, bạn có thể cô lập bạn "chi nhánh thời gian biên dịch" tới chức năng mà thoả thuận độc quyền với gọi số on_error và gọi hàm mới thay vì on_error bên trong visit_impl::call.

template<class DerivedClass> 
static auto call(Base&& base, const F& f, const E& on_error) { 
    if (typeid(base) == typeid(DerivedClass)) { 
     using Derived = match_cvref_t<Base, DerivedClass>; 
     return f(std::forward<Derived>(static_cast<Derived&&>(base))); 
    } else { 
     return error_dispatch<F, Derived>(on_error); 
//    ^^^^^^^^^^^^^^^^^^^^^^^^^ 
    } 
} 

template <typename F, typename Derived, typename E> 
auto error_dispatch(const E& on_error) 
    -> std::enable_if_t<is_void_v<decltype(on_error())>> 
{ 
    on_error(); 
    throw Dummy(); 
} 

template <typename F, typename Derived, typename E> 
auto error_dispatch(const E& on_error) 
    -> std::enable_if_t< 
     is_same_v<decltype(on_error()), 
        decltype(std::declval<const F&>()(Derived{}))> 
    > 
{ 
    return on_error(); 
} 
+1

1. Nếu 'f' trả về' void' thì sao? 2. Tại sao không thể 'on_error' trả về một cái gì đó có thể chuyển đổi thành kiểu kết quả? –

+0

Đây thực chất là những gì tôi đã làm, ngoại trừ việc tôi rời khỏi điều kiện SFINAE khỏi nhánh 'is_same_v' để nó hoạt động nếu' on_error' trả về một cái gì đó có thể chuyển đổi thành kiểu kết quả, và do đó nó sẽ gặp lỗi trường hợp khác. ... Nhưng T.C. đặt ra một điểm tốt về "nếu' f' trả về void? ", vì vậy bây giờ tôi sẽ phải thay đổi điều đó. :) – Quuxplusone

1

Làm thế nào về việc sử dụng variant (std C++ 17, hoặc thúc đẩy một)? (Và sử dụng truy cập tĩnh)

using BaseValue = std::variant<int, double, std::string>; 

struct bin_op 
{ 
    void operator() (int, double) const { std::cout << "int double\n"; } 
    void operator() (const std::string&, const std::string&) const 
    { std::cout << "strings\n"; } 

    template <typename T1, typename T2> 
    void operator() (const T1&, const T2&) const { std::cout << "other\n"; /* Or throw */ } 
}; 


int main(){ 
    BaseValue vi{42}; 
    BaseValue vd{42.5}; 
    BaseValue vs{std::string("Hello")}; 

    std::cout << (vi == vd) << std::endl; 

    std::visit(bin_op{}, vi, vd); 
    std::visit(bin_op{}, vs, vs); 
    std::visit(bin_op{}, vi, vs); 
} 

Demo

+0

Không, thay thế hệ thống phân cấp đa hình bằng một 'biến thể 'không đa hình là đúng. – Quuxplusone

+0

Hệ thống phân cấp có được cố định không? nếu có, bạn vẫn có thể áp dụng [mẫu khách truy cập] (https://stackoverflow.com/documentation/design-patterns/4579/visitor-pattern/15127/visitor-pattern-example-in-c#t=201706022117345811126). (và sau đó có thể [nhiều công văn] (https://stackoverflow.com/a/29345504/2684539)). – Jarod42

+0

Tôi tin câu hỏi của bạn được trả lời bởi câu này trong OP của tôi: "Ví dụ, nếu tôi muốn thêm' bool does_be_greater (BaseValue *, UserInput) ', điều đó sẽ yêu cầu một phương thức ảo toàn bộ với N triển khai phân tán trên phân cấp. " Tôi sẽ nghiên cứu https: // stackoverflow.com/questions/29286381/multiple-dispatch-solution-with-full-maintenanceability/29345504 # 29345504 và xem liệu nó có thể áp dụng hay không. – Quuxplusone

Các vấn đề liên quan