草庐IT

c++ - 实现这个 "on error, throw"回调的最佳方式是什么?

coder 2024-02-11 原文

请注意:在我们开始真正的问题之前,这里有很多背景信息。

我有一个相当广泛的 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采用通用 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>简单测试 typeid反对A , 如果是,则调用 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();}

  • 否则,静态断言

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

最佳答案

I guess one way to do it would be write several specializations of the base case

除此之外,您可以将“编译时分支”隔离到一个专门处理调用 on_error 的函数,并调用该新函数而不是 on_errorvisit_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();
}

关于c++ - 实现这个 "on error, throw"回调的最佳方式是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44334895/

有关c++ - 实现这个 "on error, throw"回调的最佳方式是什么?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  3. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  4. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  5. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  6. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  7. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  10. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

随机推荐