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

huangapple go评论59阅读模式
英文:

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

问题

首先,我知道C++中可变参数函数不能是虚拟的。我的问题是如何模拟下面这个“不正确”的示例。我想要一个类A,其中有一个“虚拟可变参数函数”,以及一个类B,它继承自A并只实现了其中的“一部分”:

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

class B : public A {
// 仅覆盖了“通用”f的2个选项
void f(unsigned char* buff2) override;
void f(unsigned char* buff1, unsigned int* buff2) override;
};

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

A* a = factory(...);
a->f(buff1, buff2, buff3); // 正常
a->f(buff1); // 正常
a->f(buff1, buff2); // 错误!工厂提供了一个不支持此调用的实例

<details>
<summary>英文:</summary>

First of all, I know that variadic functions can&#39;t be virtual in c++. My question is how to emulate the next &quot;incorrect&quot; example. I want to have a class A with &quot;virtual variadic function&quot; and class B which inherits it and implements only &quot;part of it&quot;:

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;
};


In my use case it is very important to have this generic `f` call but different classes only support &quot;subsets&quot; of variadic function `f`:

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


</details>


# 答案1
**得分**: 2

Instead of virtual, you might use `std::variant` and using overload as dispatch:

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"; }


And then

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);
}


[Demo](https://godbolt.org/z/YhzoTE6xx)

<details>
<summary>英文:</summary>

Instead of virtual, you might use `std::variant` and using overload as dispatch:

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"; }


And then

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);
}


[Demo](https://godbolt.org/z/YhzoTE6xx)


</details>



# 答案2
**得分**: 1

以下是要翻译的内容:

自各种 `f` 函数实际上除了它们的名称之外并没有共享任何东西,所以我将它们建模为实际的独立接口,这与处理对象可选实现接口的现有面向对象编程工具更加契合(例如 `dynamic_cast`)。然后,`A` 及其自己的 `f` 模板只是隐藏了交叉转换和错误处理。

```cpp
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) {
            // 错误:动态类型无法处理这些参数。
        }
        
        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上查看实时演示

英文:

Since the various f functions really share nothing except their name, I've modeled them as actual separate interfaces, which meshes way better with the existing OOP tools that handle objects optionally implementing interfaces (i.e. dynamic_cast). A and its own f template then just hide away the cross-casting and error handling.

template &lt;class... Types&gt;
struct HasF {
    virtual void f(Types... buffers) = 0;
};

struct A {
    virtual ~A() = default;

    template &lt;class... Types&gt;
    void f(Types... buffers) {
        auto *const p = dynamic_cast&lt;HasF&lt;Types...&gt;*&gt;(this);

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

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

See it live on Godbolt.org

答案3

得分: 0

It sounds like you are looking for 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
    }
};

However your requirements imply a horrible design problem. Someone who is given an A can't know what they can do with it without knowing what subclass it actually is, at which point they should have a reference to that type, instead.

英文:

It sounds like you are looking for std::any.

class A {
public:
template&lt;class... Types&gt;
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 &quot;generic&quot; f
void f_impl(std::any arg) override
{
    if (auto * tup = std::any_cast&lt;std::tuple&lt;unsigned char*&gt;&gt;(&amp;arg)) {
        // single arg case
    } else if (auto * tup = std::any_cast&lt;std::tuple&lt;unsigned char*, unsigned char*&gt;&gt;(&amp;arg)) {
        // two arg case
    } else {
        throw std::runtime_error(&quot;unsupported call&quot;); // or whatever
    }
};

However your requirements imply a horrible design problem. Someone who is given an A can't know what they can do with it without knowing what subclass it actually is, at which point they should have a reference to that type, instead.

huangapple
  • 本文由 发表于 2023年5月25日 21:56:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76333096.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定