1

I am trying to create something similar to a type-safe multiqueue. The idea is that when I push an item, it will be added to a queue consisting of objects of the same type.

All the queues will have a common interface(the queue_intf in this case) that will do some processing.

The code I came up is the following:

#include <queue>
#include <vector>
#include <memory>
#include <stdlib.h>

class queue_intf {
public:
    virtual void process(void) = 0;
};

template <typename T>
class queue : public queue_intf {
public:
    std::queue<T> q_;
    static const char* name()
    {
        return typeid(T).name();
    }
    virtual void process(void) override {
        printf("process: %s\n", this->name());
    }
    void push(T &a) {
        printf("push: %s\n", this->name());
    }
};

template <typename...>
class queues {
public:
    std::vector<queue_intf *> qs_;
    void process(void) {
        for (auto q: this->qs_) {
            q->process();
        }
    }
};

template <typename T, typename... Ts>
class queues<T, Ts...> : public queues<Ts...> {
public:
    queue<T> q_;
    queues() {
        this->qs_.push_back(&this->q_);
    }
    void push(T &v) {
        q_.push(v);
    }
};

class a {
};

class b {
};

int
main (int argc, char *argv[])
{
    queues<a, b> qs;
    a ai;
    b bi;

    qs.push(ai);
    qs.process();
    qs.push(bi);
}

However when I compile it I get the following error:

main.cc: In function ‘int main(int, char**)’:
main.cc:65:15: error: no matching function for call to ‘queues<a, b>::push(b&)’
     qs.push(bi);
               ^
main.cc:45:10: note: candidate: void queues<T, Ts ...>::push(T&) [with T = a; Ts = {b}]
     void push(T &v) {
          ^~~~
main.cc:45:10: note:   no known conversion for argument 1 from ‘b’ to ‘a&’

I was expecting the queue class to have void push(b &v) method, but it seems it doesn't.

Any idea why?

Edit:

Here is a smaller example (no std:vector or anything):

template <typename...>
class queues {
};

template <typename T, typename... Ts>
class queues<T, Ts...> : public queues<Ts...> {
public:
    void push(T &v) {
    }
};

class a {
};

class b {
};

int
main (int argc, char *argv[])
{
    queues<a, b> qs;
    a ai;
    b bi;

    qs.push(ai);
    qs.push(bi);
}
38489344
  • 138
  • 8
  • This has nothing to do with variadic templates. If I'm understanding the code correctly, the shown code assumes that a `std::vector *` can be converted to a `std::vector *`, where `T` is derived from `queue_intf`. Unfortunately, you cannot do that. A vector containing derived objects cannot be converted to a vector containing the parent object. C++ does not work this way. – Sam Varshavchik Oct 31 '20 at 00:41
  • @SamVarshavchik not sure I see the problem you are referring. I am not converting `std::vector *` to `std::vector *`. I am adding `queue *` which implements `queue_intf` to `std::vector` which I believe is fine. That's not what the compiler complains about. – 38489344 Oct 31 '20 at 00:43
  • 2
    Hmmm. Looks like you need to a `using queues::push` (in the specialization), in order to get overload resolution working. – Sam Varshavchik Oct 31 '20 at 00:48
  • @SamVarshavchik worked! Thanks! – 38489344 Oct 31 '20 at 00:51
  • 1
    A perhaps useful reference: [Why does an overridden function in the derived class hide other overloads of the base class?](https://stackoverflow.com/questions/1628768) – JaMiT Oct 31 '20 at 00:57

1 Answers1

1

To explain this, let's use a simplified example, you have just two classes:

class base {

public:
    void method(int *);
};

class derived : public base {

public:

    void method(char *);
};

And you attempt to call it:

derived d;

int i;

d.method(&i);

This fails. Name lookup starts at the given derived class, and finds a matching class method with the specified name: method. However its parameter doesn't match, so this is ill-formed. If the derived class had both methods, overload resolution will pick the matching one. But once a class method with the specified name is found any parent class will not be searched for any other methods with the same name. Only if the derived class does not have any methods with the specified name does name lookup continue with the parent class(es).

Putting

using base::method;

In the derived class imports base's method into the derived's namespace, at which point everything works just like with regular overload resolution.

This is why you need a using declaration with your recursive templates. The second derived template imports it from the first one, the next one imports everything from the second derived template, and so on.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148