实现此"on error, throw"回调的最佳方法是什么?
What's the best way to implement this "on error, throw" callback?
请警告:在我们解决真实问题之前,这里有很多背景信息。
我有一个相当宽的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
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 在C++中向零方向近似的最佳方法
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 检测win32服务创建和删除的最佳方法
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- 在 c++ 中对类中的 c 字符串动态数组进行排序的最佳方法是什么?
- 将线程中的数据存储到全局容器的最佳方法?
- 将一系列整数放入类的最佳方法是什么?
- 在派生类中使用基类的私有成员变量的最佳方法
- 在 C++ 中将非指定类型作为参数传递的最佳方法?
- Qt - QVector 和模型视图 - 从列表视图获取自定义类的最佳方法是什么?
- 使用 Git 处理 C++ Visual Studio 2019 解决方案的外部依赖项源代码管理的最佳方法是什么?
- 比较两个节点坐标的最佳方法是什么?
- 在nodejs中使用本机代码的最佳方法是什么?
- 将 pybind11 绑定标记为已弃用的最佳方法
- C++:将向量传递到构造函数以创建成员变量的最佳方法?
- C++中变量混叠的最佳方法
- 读取大文件(>2GB)(文本文件包含以太网数据)并通过不同参数随机访问数据的最佳方法是什么?