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

112 Views Asked by At

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

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 "subsets" 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
3

There are 3 best solutions below

0
Caleth On

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.

0
Jarod42 On

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

0
Quentin On

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 <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 { /* ... */ }
};

See it live on Godbolt.org