如何模拟“虚拟可变参数函数”部分的覆盖?

How to emulate override of parts of "virtual variadic functions"?

提问人:toxic 提问时间:5/25/2023 更新时间:5/25/2023 访问量:97

问:

首先,我知道可变参数函数在 c++ 中不能是虚拟的。我的问题是如何模仿下一个“不正确”的例子。我想要一个具有“虚拟可变参数函数”的类 A,以及继承它并仅实现“其中的一部分”的类 B:

class A {
template<class... Types>
virtual void f(Types... buffers) { return; }
};

class B : public A {
// Overrides only 2 options of "generic" f
void f(unsigned char* buff2) override;
void f(unsigned char* buff1, unsigned int* buff2) override;
};

在我的用例中,拥有这个泛型调用非常重要,但不同的类只支持可变参数函数的“子集”:ff

A* a= factory(...);
a->f(buff1, buff2, buff3); // OK
a->f(buff1); // OK
a->f(buff1, buff2); // Error! Factory gave an instance which does not support this call
C++ variadic-templates 虚拟继承

评论

0赞 Jarod42 5/25/2023
子类型是固定的吗?
1赞 Nelfeal 5/25/2023
我不清楚您打算如何声明虚拟函数。您知道函数模板不能是虚拟的,但您仍然想要多态性?
4赞 NathanOliver 5/25/2023
多态性背后的整个想法是你声明一个接口,然后派生类实现它,这样你就不必关心调用了什么函数。这听起来像是 XY 问题。你想用这个解决什么?
0赞 CoffeeTableEspresso 5/25/2023
如果人们不知道什么是类型,他们怎么知道要调用什么函数?aa

答:

0赞 Caleth 5/25/2023 #1

听起来你正在寻找.std::any

class A {
public:
template<class... Types>
void f(Types... buffers) { return f_impl(std::tuple(buffers...)); }
private:
virtual void f_impl(std::any arg) = 0;
};

class B : public A {
// Overrides only 2 options of "generic" f
void f_impl(std::any arg) override
{
    if (auto * tup = std::any_cast<std::tuple<unsigned char*>>(&arg)) {
        // single arg case
    } else if (auto * tup = std::any_cast<std::tuple<unsigned char*, unsigned char*>>(&arg)) {
        // two arg case
    } else {
        throw std::runtime_error("unsupported call"); // or whatever
    }
};

但是,您的要求意味着一个可怕的设计问题。如果不知道它到底是什么子类,就不知道他们能用它做什么,这时他们应该引用该类型。A

2赞 Jarod42 5/25/2023 #2

您可以使用和使用重载作为调度,而不是虚拟:std::variant

class A {};
class B : public A {};

template<class... Types>
void f(A&, Types... buffers) { std::cout << "A" << sizeof...(buffers) << std::endl; }

void f(B&, unsigned char* buff1) { std::cout << "B1\n"; }
void f(B&, unsigned char* buff1, unsigned int* buff2) { std::cout << "B2\n"; }

然后

std::variant<A, B> vars[] =  {A{}, B{}};

unsigned char buff1[42]{};
unsigned int buff2[42]{};

for (auto& var : vars) {
    std::visit([&](auto& a_or_b) { f(a_or_b, buff1); }, var);
    std::visit([&](auto& a_or_b) { f(a_or_b, buff1, buff2); }, var);
    std::visit([](auto& a_or_b) { f(a_or_b); }, var);
}

演示

1赞 Quentin 5/25/2023 #3

由于各种函数除了它们的名称之外实际上没有任何共享,因此我将它们建模为实际的独立接口,这与现有的 OOP 工具更好地啮合,这些工具可以处理有选择地实现接口的对象(即 )。 然后,它自己的模板只是隐藏了交叉转换和错误处理。fdynamic_castAf

template <class... Types>
struct HasF {
    virtual void f(Types... buffers) = 0;
};

struct A {
    virtual ~A() = default;

    template <class... Types>
    void f(Types... buffers) {
        auto *const p = dynamic_cast<HasF<Types...>*>(this);

        if(!p) {
            // Error: the dynamic type cannot handle these arguments.
        }
        
        p->f(buffers...);
    }
};

struct B
: A
, HasF<unsigned char *>
, HasF<unsigned char *, unsigned int *> {
    void f(unsigned char* buff2) override { /* ... */ }
    void f(unsigned char* buff1, unsigned int* buff2) override { /* ... */ }
};

在 Godbolt.org 上观看直播