用于访问多态类型的类似于 std::visit 的函数

A std::visit-like function for visiting over polymorphic types

我最近在尝试 C++17 的 std::variantstd::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 使用虚函数似乎并不简单,因为它取决于两个输入的类型。 此外,我们正在努力避免这些功能的虚拟方法, 因为我们更喜欢让 IFooIBar 的接口与这些外部函数无关。

对于访问函数的多个输入对象,使用访问者模式似乎也不合理。

最简单的方法似乎是使用我上面显示的 dynamic_cast 的示例实现, 但是当我们从 1 个输入到 N 个输入时,这会迅速增加要写入的案例数量。 不过,上面的 std::variant+std::visit 方法非常清晰直接地涵盖了这种情况。

最后,我们的 constraints/requirements 是:

这有可能吗?

我正在考虑编写一个可变递归模板函数,类似于 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");
}