1

I'm trying to make an interface an abstract class which would be implemented by derivated childs. One of methods in that class has to be variadic (children get one ore more QSharedPointer<QObject> depending on implementation)

The problem is that:

  1. templated methods cannot be virtual

  2. I cannot make a variadic method taking QSharedPointer<QObject>... args arguments because of error: expansion pattern ‘QSharedPointer<QObject>’ contains no argument packs.

Less words, more code:

class BaseClass {
public:
    virtual void foo(QSharedPointer<QObject>... args) = 0;
}

class ChildClassA : public BaseClass {
public:
    void foo(QSharedPointer<QObject> arg1);
}


class ChildClassB : public BaseClass {
public:
    void foo(QSharedPointer<QObject> arg1, QSharedPointer<QObject> arg2);
}

I would like to use above classes to things like that:

template <class T = BaseClass>
class Controller<T>{
    void callFoo(QSharedPointer<QObject>... args){
        T* = new T();
        T->foo(args);
    }
}

As you can see the BaseClass is only to say: use one of my children as generic type.

How do I make such things work? Is it even possible in C++?

Luke
  • 2,350
  • 6
  • 26
  • 41
  • 2
    How about taking in a vector of smart pointers? – Quentin Nov 23 '17 at 16:07
  • @Quentin kinda 'meh' for me because you can pass empty vector. Also, it's pointless to wrap only one object into wrapper every time when using `ChildClassA` – Luke Nov 23 '17 at 16:09
  • 1
    If you want to optimize for known-child access, you can keep the N-parameters function but use a vector to override the virtual one. The issue is that when calling `foo` on an object with static type `BaseClass`, C++ doesn't know how many parameters the child expects. Which is kind of a weird design as well: how do *you* know the number of parameters? What should happen in case of a mismatch? Would separating the children by arity be a solution? – Quentin Nov 23 '17 at 16:15
  • You could always resort to `virtual void foo(...) = 0;`, but I don't know if you really want that. – Passer By Nov 23 '17 at 16:17
  • @Quentin actually I know how many parameters the child method will take because a generic 'controller' class works for specified Child class. E.g. `template class GenericClass{}` The only purpose of BaseClass is to be interface that says that one of Child classes must be used for that generic slot. – Luke Nov 23 '17 at 16:19
  • Then use that controller to decide on calling one of the many overloads of the function? – Passer By Nov 23 '17 at 16:23
  • @Luke that's great! Then we can have children inherit from `BaseClass` and implement the corresponding N-parameters function, and not much changes because you're already in a template. Right? – Quentin Nov 23 '17 at 16:23
  • @Quentin I think I don't exactly get what you mean. I extended the sample code. Is your idea still applicable? – Luke Nov 23 '17 at 16:29
  • @PasserBy overloaded methods in Controller is a nice idea but isn't what I am looking for. Both methods will be accessible while I want only one method accessible depending on the class specified in the generic template – Luke Nov 23 '17 at 16:31
  • What I meant was, since the number of arguments is known from the controller, just use the controller to call the right overload of the function, and overload all the possible number of arguments. – Passer By Nov 23 '17 at 16:33

2 Answers2

4

Since (per the comments) you already have separated the children by arity, and they're managed by separate instantiations of a Controller class template, there is no point in trying to force them all into the same hierarchy -- that only means workarounds and runtime overhead.

Instead, let's make BaseClass a template, and use its parmeter to generate foo's signature.

namespace detail {
    template <std::size_t, class T>
    using expandHook = T;
}

template <std::size_t N, class = std::make_index_sequence<N>>
struct BaseClass;

template <std::size_t N, std::size_t... Idx>
struct BaseClass<N, std::index_sequence<Idx...>> {
    virtual void foo(detail::expandHook<Idx, QSharedPointer<QObject>>... args) = 0;
};

detail::expandHook is kind of a syntactic trick to repeat the QSharedPointer<QObject> type as many times as there are Idxes, which are 0 to N - 1 thanks to the work of std::index_sequence.

Then, children inherit from the corresponding BaseClass:

struct Child : BaseClass<2> {
    void foo(QSharedPointer<QObject>, QSharedPointer<QObject>) override;
};

Bonus: a child can inherit from several BaseClass specializations if you want it to support several arities!

Finally, a controller will be parameterized by the adequate BaseClass type, and everything clicks in.

See it live on Coliru (with stubbed types, and main's content would be what's inside a Controller).

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • **Note: you need to use C++14 or newer, QT in 2017 uses C++11 by default** Nevertheless, great solution! Thanks! – Luke Nov 23 '17 at 17:05
  • 1
    @Luke ... and [here's the Q&A](https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence) about implementing `std::index_sequence` in C++11 :) – Quentin Nov 23 '17 at 17:07
1

If you follow the guideline that public functions should be non-virtual and virtual functions should be private, then you can solve this problem by turning the base function into a variadic template function which delegates to a virtual private function taking a std::vector.

This also has the advantage that all the unpacking happens in one place and derived classes are easier to implement.

Here is an example (I've replaced QSharedPtr with std::shared_ptr and added dummy implementations for QObject so that the example can be compiled without 3rd-party stuff):

#include <memory>
#include <vector>
#include <iostream>
#include <cassert>

struct QObject { virtual void sayHi() const = 0; };
struct DerivedQObject1 : public QObject { void sayHi() const override { std::cout << "1\n"; } };
struct DerivedQObject2 : public QObject { void sayHi() const override  { std::cout << "2\n"; } };

class BaseClass {
public:
    template <class... Types>
    void foo(std::shared_ptr<Types>... args)
    {
        std::vector<std::shared_ptr<QObject>> vector;
        pushBack(vector, args...);
        assert(!empty(vector));
        doFoo(vector);
    }

private:
    virtual void doFoo(std::vector<std::shared_ptr<QObject>> const& args) = 0;

    template<typename LastType>
    static void pushBack(std::vector<std::shared_ptr<QObject>>& vector, LastType arg)
    {
        vector.push_back(arg);
    };

    template<typename FirstType, typename ...OtherTypes>
    static void pushBack(std::vector<std::shared_ptr<QObject>>& vector, FirstType const& firstArg, OtherTypes... otherArgs)
    {
        vector.push_back(firstArg);
        pushBack(vector, otherArgs...);
    };
};

class ChildClassA : public BaseClass {
private:
    void doFoo(std::vector<std::shared_ptr<QObject>> const& args) override;
};

void ChildClassA::doFoo(std::vector<std::shared_ptr<QObject>> const& args) {
    for (auto const& arg : args) {
        arg->sayHi();
    }
}

int main() {
    ChildClassA child;
    auto obj1 = std::make_shared<DerivedQObject1>();
    auto obj2 = std::make_shared<DerivedQObject2>();
    child.foo(obj1, obj2);
}
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62