用于访问多态类型的类似于 std::visit 的函数
A std::visit-like function for visiting over polymorphic types
我最近在尝试 C++17 的 std::variant
和 std::visit
,我发现它非常强大。
我特别喜欢在多个变体对象上创建访问者模式的功能。
这是我的意思的一个例子:
std::variant<int, float, char> v1 { 's' };
std::variant<int, float, char> v2 { 10 };
std::visit(overloaded{
[](int a, int b) { },
[](int a, float b) { },
[](int a, char b) { },
[](float a, int b) { },
[](auto a, auto b) { }, // << default!
}, v1, v2);
有关完整详细信息,请参阅 https://www.bfilipek.com/2018/09/visit-variants.html。
鉴于此,我想知道是否可以基于多态类型而不是变体对象编写类似的代码。
想一想我们使用动态多态性和父对象来编写通用接口的场景。
然后我们要实现依赖于几种多态类型的特定功能,即 like
void fun(IFoo* ptr_foo, IBar* ptr_bar) {
{
Foo1* child_foo = dynamic_cast<Foo1*>(ptr_foo);
Bar1* child_bar = dynamic_cast<Bar1*>(ptr_bar);
if(child_foo && child_bar) { return fun(child_foo, child_bar) }
}
// ... other cases
{
Foo1* child_foo = dynamic_cast<Foo1*>(ptr_foo);
BarN* child_bar = dynamic_cast<BarN*>(ptr_bar);
if(child_foo && child_bar) { return fun(child_foo, child_bar) }
}
// ... other cases
{
FooN* child_foo = dynamic_cast<FooN*>(ptr_foo);
BarN* child_bar = dynamic_cast<BarN*>(ptr_bar);
if(child_foo && child_bar) { return fun(child_foo, child_bar) }
}
throw std::runtime_error{};
}
我知道上述情况远非最佳,但只是试图让场景尽可能清晰。
在这种情况下,为 fun
使用虚函数似乎并不简单,因为它取决于两个输入的类型。
此外,我们正在努力避免这些功能的虚拟方法,
因为我们更喜欢让 IFoo
或 IBar
的接口与这些外部函数无关。
对于访问函数的多个输入对象,使用访问者模式似乎也不合理。
最简单的方法似乎是使用我上面显示的 dynamic_cast
的示例实现,
但是当我们从 1 个输入到 N 个输入时,这会迅速增加要写入的案例数量。
不过,上面的 std::variant
+std::visit
方法非常清晰直接地涵盖了这种情况。
最后,我们的 constraints/requirements 是:
- 以多个多态对象作为输入的访问者模式
- 非关键运行时性能(所以说使用
dynamic_cast
没问题)
- 具有最小样板,类似于
std::visit
方法
这有可能吗?
我正在考虑编写一个可变递归模板函数,类似于 std::visit
,
这将自动生成所有类型的案例来检查。
使用示例如下:
visitPolymorphic<tuple<Foo1, Foo2>,tuple<Bar1, Bar2, Bar3>>(ptr_foo, ptr_bar)
这将 if-else 处理不同的模板输入类型,并调度正确的调用。
对此有什么想法吗?
你也可以在那里使用 std::variant
:
struct Foo1;
struct Foo2;
struct Foo3;
using FooVariant = std::variant<Foo1*, Foo2*, Foo3*>;
struct IFoo
{
virtual ~IFoo() = default;
FooVariant AsVariant() = 0;
// ...
};
struct Foo1 : IFoo
{
FooVariant AsVariant() override { return this;}
// ...
};
// Same for FooX
struct Bar1;
struct Bar2;
struct Bar3;
using BarVariant = std::variant<Bar1*, Bar2*, Bar3*>;
struct IBar
{
virtual ~IBar() = default;
BarVariant AsVariant() = 0;
// ...
};
struct Bar1 : IBar
{
BarVariant AsVariant() override { return this;}
// ...
};
// Same for BarX
然后
void fun(IFoo& foo, IBar& bar) {
std::visit(overloaded{
[](Foo1* a, Bar1* b) { /*..*/ },
[](Foo2* a, Bar2* b) { /*..*/ },
[](Foo3* a, auto* b) { /*..*/ },
[](auto* a, auto* b) { /*..*/ }, // << default!
},
foo.AsVariant(), bar.AsVariant()
);
}
如果您不想在界面中使用虚拟 AsVariant()
(但使用 dynamic_cast
),您可能仍然有免费功能:
FooVariant AsVariant(IFoo& foo)
{
if (auto* p = dynamic_cast<Foo1*>(&foo)) {
return p;
}
if (auto* p = dynamic_cast<Foo2*>(&foo)) {
return p;
}
if (auto* p = dynamic_cast<Foo3*>(&foo)) {
return p;
}
throw std::runtime_error("Invalid type");
}
我最近在尝试 C++17 的 std::variant
和 std::visit
,我发现它非常强大。
我特别喜欢在多个变体对象上创建访问者模式的功能。
这是我的意思的一个例子:
std::variant<int, float, char> v1 { 's' };
std::variant<int, float, char> v2 { 10 };
std::visit(overloaded{
[](int a, int b) { },
[](int a, float b) { },
[](int a, char b) { },
[](float a, int b) { },
[](auto a, auto b) { }, // << default!
}, v1, v2);
有关完整详细信息,请参阅 https://www.bfilipek.com/2018/09/visit-variants.html。 鉴于此,我想知道是否可以基于多态类型而不是变体对象编写类似的代码。
想一想我们使用动态多态性和父对象来编写通用接口的场景。 然后我们要实现依赖于几种多态类型的特定功能,即 like
void fun(IFoo* ptr_foo, IBar* ptr_bar) {
{
Foo1* child_foo = dynamic_cast<Foo1*>(ptr_foo);
Bar1* child_bar = dynamic_cast<Bar1*>(ptr_bar);
if(child_foo && child_bar) { return fun(child_foo, child_bar) }
}
// ... other cases
{
Foo1* child_foo = dynamic_cast<Foo1*>(ptr_foo);
BarN* child_bar = dynamic_cast<BarN*>(ptr_bar);
if(child_foo && child_bar) { return fun(child_foo, child_bar) }
}
// ... other cases
{
FooN* child_foo = dynamic_cast<FooN*>(ptr_foo);
BarN* child_bar = dynamic_cast<BarN*>(ptr_bar);
if(child_foo && child_bar) { return fun(child_foo, child_bar) }
}
throw std::runtime_error{};
}
我知道上述情况远非最佳,但只是试图让场景尽可能清晰。
在这种情况下,为 fun
使用虚函数似乎并不简单,因为它取决于两个输入的类型。
此外,我们正在努力避免这些功能的虚拟方法,
因为我们更喜欢让 IFoo
或 IBar
的接口与这些外部函数无关。
对于访问函数的多个输入对象,使用访问者模式似乎也不合理。
最简单的方法似乎是使用我上面显示的 dynamic_cast
的示例实现,
但是当我们从 1 个输入到 N 个输入时,这会迅速增加要写入的案例数量。
不过,上面的 std::variant
+std::visit
方法非常清晰直接地涵盖了这种情况。
最后,我们的 constraints/requirements 是:
- 以多个多态对象作为输入的访问者模式
- 非关键运行时性能(所以说使用
dynamic_cast
没问题) - 具有最小样板,类似于
std::visit
方法
这有可能吗?
我正在考虑编写一个可变递归模板函数,类似于 std::visit
,
这将自动生成所有类型的案例来检查。
使用示例如下:
visitPolymorphic<tuple<Foo1, Foo2>,tuple<Bar1, Bar2, Bar3>>(ptr_foo, ptr_bar)
这将 if-else 处理不同的模板输入类型,并调度正确的调用。
对此有什么想法吗?
你也可以在那里使用 std::variant
:
struct Foo1;
struct Foo2;
struct Foo3;
using FooVariant = std::variant<Foo1*, Foo2*, Foo3*>;
struct IFoo
{
virtual ~IFoo() = default;
FooVariant AsVariant() = 0;
// ...
};
struct Foo1 : IFoo
{
FooVariant AsVariant() override { return this;}
// ...
};
// Same for FooX
struct Bar1;
struct Bar2;
struct Bar3;
using BarVariant = std::variant<Bar1*, Bar2*, Bar3*>;
struct IBar
{
virtual ~IBar() = default;
BarVariant AsVariant() = 0;
// ...
};
struct Bar1 : IBar
{
BarVariant AsVariant() override { return this;}
// ...
};
// Same for BarX
然后
void fun(IFoo& foo, IBar& bar) {
std::visit(overloaded{
[](Foo1* a, Bar1* b) { /*..*/ },
[](Foo2* a, Bar2* b) { /*..*/ },
[](Foo3* a, auto* b) { /*..*/ },
[](auto* a, auto* b) { /*..*/ }, // << default!
},
foo.AsVariant(), bar.AsVariant()
);
}
如果您不想在界面中使用虚拟 AsVariant()
(但使用 dynamic_cast
),您可能仍然有免费功能:
FooVariant AsVariant(IFoo& foo)
{
if (auto* p = dynamic_cast<Foo1*>(&foo)) {
return p;
}
if (auto* p = dynamic_cast<Foo2*>(&foo)) {
return p;
}
if (auto* p = dynamic_cast<Foo3*>(&foo)) {
return p;
}
throw std::runtime_error("Invalid type");
}