实现此"on error, throw"回调的最佳方法是什么?

What's the best way to implement this "on error, throw" callback?

本文关键字:最佳 方法 是什么 回调 throw on error 实现      更新时间:2023-10-16

请警告:在我们解决真实问题之前,这里有很多背景信息。

我有一个相当宽的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; };

因此,编写匹配器的一种方法 - "用户的输入是否等于此基础价值的值?" - 会这样:

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中,采用通用lambda:

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的问题在于,纠错行为"投掷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();}

  • 否则,static-assert

,但我觉得我缺少一些简单的方法。谁能看到它?

我想一种方法是写几个基本情况的专业

而不是这样做,可以将"编译时间分支"隔离到专门用于调用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();
}

使用 variant(std c 17,还是提高一个(怎么样?(并使用静态访问者(

using BaseValue = std::variant<int, double, std::string>;
struct bin_op
{
    void operator() (int, double) const { std::cout << "int doublen"; }
    void operator() (const std::string&, const std::string&) const
    { std::cout << "stringsn"; }
    template <typename T1, typename T2>
    void operator() (const T1&, const T2&) const { std::cout << "othern"; /* 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