Đượ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áokhi
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?
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. –
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 –