1

I'm trying to create a functional inheritance hierarchy. We are working with implementing our own priority queue class template, and the program includes lists such as buy orders.

My idea was to have "p_queue" as the base class, and let the "orders" class inherit from it, and then let subclasses such as "buy_orders" inherit from the orders class. My reasoning is that every order in this program is a priority queue, as they will be sorted by it.

Normally I would understand the error "type 'base_class' is not a direct base of 'sub_class'" if the derived class wasn't directly inheriting the superclass. But since the orders class inherits the p_queue class and no other class directly inherits from priority queue class, I'm confused why I get this error message.

Here is the code

//main.cpp
#include "orders.h"

int main(){
    
    

    return 0;
}
//orders.h
#include "p_queue.h"

template<typename T>
struct less{
    bool operator()(const T& a, const T& b){
        return a < b;
    }
};

template<typename T>
class Orders : public p_queue<T, decltype(less<T>())> {
private:
    size_t m_Size;
    std::vector<T> list;
public:
    Orders(const size_t& sz, const std::vector<T>& l) : list(l), m_Size(sz),
    p_queue<T>(&(*list.begin()), &(*list.end()), less<T>()){}

    virtual const size_t getSize() const { return m_Size; }
    virtual const std::vector<T> getList() const = 0;
};

struct buy_orders : public Orders<size_t>{
    std::vector<size_t> buy_prices;
    buy_orders(const size_t& sz) : Orders(sz, buy_prices) {}
};
//p_queue.h
#include <functional>

template<typename T, typename Compare = std::less<T>>
class p_queue{
protected:
    T* m_first;
    T* m_last;
    Compare m_comp;
public:
    p_queue(T* first, T* last, Compare comp = Compare()) :
    m_first(first), m_last(last), m_comp(comp) {}
};

The above code produces the error:

<source>: In instantiation of 'Orders<T>::Orders(const size_t&, const std::vector<T>&) [with T = long unsigned int; size_t = long unsigned int]':
<source>:39:57:   required from here
<source>:31:59: error: type 'p_queue<long unsigned int, std::less<long unsigned int> >' is not a direct base of 'Orders<long unsigned int>'
   31 |     p_queue<T>(&(*list.begin()), &(*list.end()), less<T>()){}
      |                                                           ^
<source>:31:59: error: no matching function for call to 'p_queue<long unsigned int, less<long unsigned int> >::p_queue()'
<source>:13:5: note: candidate: 'p_queue<T, Compare>::p_queue(T*, T*, Compare) [with T = long unsigned int; Compare = less<long unsigned int>]'
   13 |     p_queue(T* first, T* last, Compare comp = Compare()) :
      |     ^~~~~~~
<source>:13:5: note:   candidate expects 3 arguments, 0 provided
<source>:7:7: note: candidate: 'constexpr p_queue<long unsigned int, less<long unsigned int> >::p_queue(const p_queue<long unsigned int, less<long unsigned int> >&)'
    7 | class p_queue{
      |       ^~~~~~~
<source>:7:7: note:   candidate expects 1 argument, 0 provided
<source>:7:7: note: candidate: 'constexpr p_queue<long unsigned int, less<long unsigned int> >::p_queue(p_queue<long unsigned int, less<long unsigned int> >&&)'
<source>:7:7: note:   candidate expects 1 argument, 0 provided
ASM generation compiler returned: 1
<source>: In instantiation of 'Orders<T>::Orders(const size_t&, const std::vector<T>&) [with T = long unsigned int; size_t = long unsigned int]':
<source>:39:57:   required from here
<source>:31:59: error: type 'p_queue<long unsigned int, std::less<long unsigned int> >' is not a direct base of 'Orders<long unsigned int>'
   31 |     p_queue<T>(&(*list.begin()), &(*list.end()), less<T>()){}
      |                                                           ^
<source>:31:59: error: no matching function for call to 'p_queue<long unsigned int, less<long unsigned int> >::p_queue()'
<source>:13:5: note: candidate: 'p_queue<T, Compare>::p_queue(T*, T*, Compare) [with T = long unsigned int; Compare = less<long unsigned int>]'
   13 |     p_queue(T* first, T* last, Compare comp = Compare()) :
      |     ^~~~~~~
<source>:13:5: note:   candidate expects 3 arguments, 0 provided
<source>:7:7: note: candidate: 'constexpr p_queue<long unsigned int, less<long unsigned int> >::p_queue(const p_queue<long unsigned int, less<long unsigned int> >&)'
    7 | class p_queue{
      |       ^~~~~~~
<source>:7:7: note:   candidate expects 1 argument, 0 provided
<source>:7:7: note: candidate: 'constexpr p_queue<long unsigned int, less<long unsigned int> >::p_queue(p_queue<long unsigned int, less<long unsigned int> >&&)'
<source>:7:7: note:   candidate expects 1 argument, 0 provided

If I remove the buy_orders struct from orders.h, the problem disappears. Why is this? How can I solve this issue?

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
proxylite
  • 75
  • 6
  • 2
    There is already `std::less`. So this is ambiguous. – Jason Oct 19 '22 at 06:22
  • I know of the similar question which is recommended. But maybe I am misunderstanding something here, in the other question the error arises because 'derived2' clearly isn't inheriting from the base class, because it inherits from 'derived1' which then inherits from base. But in this case my 'Orders' class gets the error, even though it is directly inheriting from the base class. – proxylite Oct 19 '22 at 06:35
  • @SergeBallesta No, the fundamental problem is still the same which is that `p_queue` is not a direct base of `Orders` while `p_queue())>` is a direct base. And since you reopened the question, I've explained the reason in my [answer](https://stackoverflow.com/a/74120903/12002570). – Jason Oct 19 '22 at 06:44
  • @proxylite You're actually inheriting from `p_queue())>` but trying to use the ctor of `p_queue` and hence the error. Basically, `p_queue())>` and `p_queue` are different class types. The ctor that you use must belong to `p_queue())>`. The solution is given in my [answer](https://stackoverflow.com/a/74120903/12002570) below. – Jason Oct 19 '22 at 06:46
  • Yes, now I understand. So easy to miss these things sometimes... Anyway, that seems to work now. Thanks again! – proxylite Oct 19 '22 at 06:49
  • Don't forget to provide the checkmark to the answer that helped you. – Captain Giraffe Oct 19 '22 at 17:34

2 Answers2

1

Problem 1

The problem is that p_queue<T> and p_queue<T, decltype(::less<T>())> are two different class-type and you're inheriting from the latter(p_queue<T, decltype(::less<T>())>) but trying to use the constructor of the former(p_queue<T>) in the member initializer list.

To solve this just make sure that you use the constructor of p_queue<T, decltype(::less<T>())> instead of p_queue<T> in the member initializer list as shown below:

template<typename T>
class Orders : public p_queue<T, decltype(::less<T>())> {
private:
    size_t m_Size;
    std::vector<T> list;
public:
    Orders(const size_t& sz, const std::vector<T>& l) : list(l), m_Size(sz),
//--------------vvvvvvvvvvvvvvvvvvvv---->added this second template argument
    p_queue<T , decltype(::less<T>())>(&(*list.begin()), &(*list.end()), ::less<T>()){}

    virtual const size_t getSize() const { return m_Size; }
    virtual const std::vector<T> getList() const = 0;
};

Working demo


Problem 2

Additionally as base class ctor will be used before the derived class member like list is initialized, and since you're using *list.begin() as an argument to the base class ctor, the program has undefined behavior.

Clang gives a warning about this:

<source>:36:43: warning: field 'list' is uninitialized when used here [-Wuninitialized]
    p_queue<T , decltype(::less<T>())>(&(*list.begin()), &(*list.end()), ::less<T>()){}
                                          ^
<source>:44:36: note: in instantiation of member function 'Orders<unsigned long>::Orders' requested here
    buy_orders(const size_t& sz) : Orders(sz, buy_prices) {}
                                   ^
<source>:36:61: warning: field 'list' is uninitialized when used here [-Wuninitialized]
    p_queue<T , decltype(::less<T>())>(&(*list.begin()), &(*list.end()), ::less<T>()){}
Jason
  • 36,170
  • 5
  • 26
  • 60
  • Oh god! I feel like a silly person now. I could have sworn I tried this, of course the constructor call has to have the same template parameters.... Thank you Jason, I sincerely appreciate your help. – proxylite Oct 19 '22 at 06:47
  • @proxylite You're welcome. Also note that since the base class ctor will be used before `list` is initialized and since you're using that uninitialized `list` in the ctor(like `*list.begin()`), you have undefined behavior. You'll get a warning about this in clang. [Demo](https://godbolt.org/z/hsjjdoPb4). – Jason Oct 19 '22 at 06:52
1

You actually have 2 problems here.

First one is simply that you have to pass explicitely the second template parameter to the p_queue constructor:

...
Orders(const size_t& sz, const std::vector<T>& l) : list(l), m_Size(sz),
    p_queue<T, decltype(less<T>())>(&(*list.begin()), &(*list.end()), less<T>()) {}
...

is enough to make the type 'base_class' is not a direct base of 'sub_class' error disappear.

But there is still another problem, even if it only raises a warning. Base classes are initialized before data members in C++, no matter the order of initializers. That means that p_queue constructor will be called before initialization of list, leading to Undefined Behaviour, because it explicitely uses the first and last members before they get a chance to be initialized. That means that you have to set up a 2 phases initialization of p_queue, with a default initialization first, and then set its m_first and m_last members in Orders constructor body:

...
p_queue(Compare comp = Compare()) : m_comp(comp) {}
p_queue(T* first, T* last, Compare comp = Compare()) :
    m_first(first), m_last(last), m_comp(comp) {}
...

and

...
Orders(const size_t& sz, const std::vector<T>& l) : list(l), m_Size(sz),
    p_queue<T, decltype(less<T>())>(less<T>()) {
    this->m_first = &(*list.begin());
    this->m_last = &(*list.end());
}
...
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Or just not make this a class hierarchy and instead make it composition. Then you can choose the order of members. – Sebastian Redl Oct 19 '22 at 06:56
  • Oh damn, I never thought about that. I will definitely try your solution. Thanks a bunch Serge! @SebastianRedl hmm yes maybe composition would be better? I didn't want to use composition here because an order doesn't *have* a priority queue, it *is* a priority queue. But maybe I'm too stuck in that line of thinking. Then I suppose the buy_orders class will have an object of orders, and the orders class an object of p_queue? – proxylite Oct 19 '22 at 07:03