2017-06-02 59 views
5

被警告:在我們遇到真正的問題之前,有很多背景信息。實現這種「錯誤,拋出」回調的最佳方式是什麼?

我有一個相當寬的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();}

  • 否則,靜態斷言

但我覺得我失去了一些更簡單的方法。任何人都可以看到它?

+1

A [MCVE]重現要沉默將是很好的編譯器警告。 –

+0

逗號運算符總是準備好被濫用:http://coliru.stacked-crooked.com/a/78f96318349b604b –

回答

2

我想一個辦法做到這一點會寫基本情況的幾個特例

而不是這樣做,你可以分離你的「編譯時分支」來與專門交易功能調用on_error,並在visit_impl::call內調用該新函數而不是on_error

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.如果'f'返回'void'會怎麼樣? 2.爲什麼'on_error'不能返回可轉換爲結果類型的東西? –

+0

這實際上是我最終做的,除了我離開了'is_same_v'分支的SFINAE條件,以便它可以工作,如果'on_error'返回可轉換爲結果類型的東西,並且它會很難另一種情況。 ......但T.C.提出了一個關於「如果f'返回無效?」的好處,所以我現在不得不去改變它。 :) – Quuxplusone

1

如何使用variant(標準C++ 17,或提高一個)? (和使用靜態訪問者)

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

不,用一個非多態的變體替代多態層次結構是正確的。 – Quuxplusone

+0

層級是否固定?如果是,您仍然可以申請[訪問者模式](https://stackoverflow.com/documentation/design-patterns/4579/visitor-pattern/15127/visitor-pattern-example-in-c#t=201706022117345811126)。 (然後可能[多派遣](https://stackoverflow.com/a/29345504/2684539))。 – Jarod42

+0

我相信你的問題在我的OP中被這句話所回答:「例如,如果我想添加'bool does_be_greater(BaseValue *,UserInput)',那就需要整個層次結構中分佈有N個實現的虛擬方法。 「我將學習https:// stackoverflow。com/questions/29286381/multiple-dispatch-solution-with-full-maintainability/29345504#29345504然後看看它是否適用。 – Quuxplusone

相關問題