被警告:在我們遇到真正的問題之前,有很多背景信息。實現這種「錯誤,拋出」回調的最佳方式是什麼?
我有一個相當寬的C++類層次結構(代表像不同類型的表達式):
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; };
而在另一邊,我有一個辦法強迫用戶輸入與預期的類型:
class UserInput { template<class T> get_as() const; };
所以寫一個匹配器的一種方法 - 「用戶的輸入是否等於這個BaseValue的值?」 - 將是這樣的:
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);
}
然而,這不結垢,無論是在方向「層次結構的寬度」,或在方向「操作數」。例如,如果我想添加bool does_be_greater(BaseValue*, UserInput)
,那需要整個層次結構中分佈有N個實現的整個虛擬方法。所以我決定走這條路,而不是:
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();
}
}
其實,我可以做一些元編程和坍塌下來到一個單一的功能visit
採取通用拉姆達:
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
被實現爲「遞歸」函數模板:my::visit<A,B,C>
簡單地根據A
測試typeid
,如果是,則調用lambda,如果不是,則調用my::visit<B,C>
。在調用堆棧的底部,my::visit<C>
對C
測試typeid,如果是,則調用lambda,如果不是,則拋出Oops()
。
好吧,現在我的實際問題!
my::visit
的問題在於錯誤行爲「throw Oops()
」是硬編碼的。我真的希望有錯誤的行爲是用戶指定的,就像這樣:
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();
});
}
我有,當我這樣做,我無法弄清楚如何實現的基類問題以這種方式編譯器會關閉不匹配的返回類型或從函數結尾掉下來!這裏是沒有on_error
回調版本:
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);
}
和這裏的什麼我真的很想有:
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);
}
也就是說,我希望能夠處理這兩種情況下:
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; });
}
所以我想這樣做會寫一些基本情況的特例一種方法:
時
is_void_v<decltype(on_error())>
,使用{on_error(); throw Dummy();}
沉默編譯器警告時
is_same_v<decltype(on_error()), decltype(f(Derived{}))>
,使用{return on_error();}
否則,靜態斷言
但我覺得我失去了一些更簡單的方法。任何人都可以看到它?
A [MCVE]重現要沉默將是很好的編譯器警告。 –
逗號運算符總是準備好被濫用:http://coliru.stacked-crooked.com/a/78f96318349b604b –