1

Possible Duplicate:
Accessing inherited variable from templated parent class

I have been implementing a binary heap as a subclass of priority_queue, and run into a situation I cannot understand, despite considerable effort. Here is the working version of the code. (Background: c is priority_queue’s backing vector.)

#include <queue>
using namespace std;

template <class nodetype>
class BinaryHeap : public priority_queue<int> {
public:
    BinaryHeap() { vec = &c; }
    vector<nodetype> *vec;
};

int main() {
    BinaryHeap<int> b;
}

However, when you change the superclass to:

class BinaryHeap : public priority_queue<nodetype>

...the compiler complains about the usage of c:

h.cpp: In constructor ‘BinaryHeap<nodetype>::BinaryHeap()’:
h.cpp:10: error: ‘c’ was not declared in this scope

This seems all the more strange because:

  1. “[c] is equivalent to this->c” (ref) – and if you use vec = &this->c, it does indeed compile.

  2. If you add a using-declaration, using priority_queue<nodetype>::c to BinaryHeap, again, it compiles. But this using-declaration ought to be unnecessary.

Edit:

So, apparently this happens because “the compiler does not look in dependent base classes when looking up nondependent names” (ref) – “c” does not depend on a template parameter, and so is nondependent, and priority_queue<nodetype> does depend on a template parameter – nodetype – so is dependent.

priority_queue<int>, on the other hand, doesn’t depend on nodetype, and so is nondependent, and so the compiler does look in it for names when resolving other nondependent names.

Is this correct? And if so:

Why does the standard mandate this behaviour, when “[looking] in dependent base classes when looking up nondependent names” is clearly possible. What is the benefit? A compile-time performance optimisation?

Community
  • 1
  • 1
Benji XVI
  • 2,192
  • 1
  • 25
  • 24
  • See http://www.parashift.com/c++-faq-lite/templates.html#faq-35.19. – Oliver Charlesworth Jan 07 '12 at 01:32
  • Thanks Oli. How unspeakably dreadful. – Benji XVI Jan 07 '12 at 01:36
  • Not strictly a duplicate. The other question is about compilers’ compliance to the standard: “Do the new Gnu C++ compiler obey the standard or not?” This is about the code problem itself: *why is the using declaration necessary*. – Benji XVI Jan 07 '12 at 01:57
  • Perhaps. But there will many other questions on precisely this topic... – Oliver Charlesworth Jan 07 '12 at 02:01
  • Also, I have to say, I floundered around trying to find the answer to this for a long time, on SO & elsewhere, with no luck. I try hard not to create SO questions lightly – this is a hard thing to solve, both through Googling and SO, and this Q could save someone encountering this problem a good deal of time. – Benji XVI Jan 07 '12 at 02:01
  • Please cite the standard, not IBM documentation. – Lightness Races in Orbit Jan 07 '12 at 02:11
  • Now that the actual question is answered: why would anybody want to do something like this? If you want to use std::priority_queue's logic you could just use std::make_heap(), std::pop_heap(), and std::push_heap(). None of the containers in the standard C++ library is intended to be derived from. You can reasonably ask why there are protected members but I'd think this was an error in a couple of classes nobody paid close attention to. – Dietmar Kühl Jan 07 '12 at 02:23
  • 1
    @DietmarKühl To make a dynamic heap container – one where you can update nodes in O(logn) time. It is actually a common, and nice, way to do it. [See here](http://www.codeguru.com/cpp/tic/tic0229.shtml) for examples. All you need do is implement up_heap, down_heap and update functions. (The exposing of `c` as public is a performance optimisation for initialisation – directly editing the vector and then re-sorting it with `make_heap` is O(n), whereas filling it using `push` is O(nlogn). I agree it’s slightly iffy, but does not seem problematic in practice.) – Benji XVI Jan 07 '12 at 11:03
  • 1
    Well guys, I respect your opinions that this a duplicate, but I must say I disagree. After my edits, this Q now asks: 1. “What is the behaviour” and 2. “What is the benefit to this behaviour”, which are both distinct from 3. “What compilers comply with the standard”. Furthermore, even were it a duplicate, it seems against the spirit of SO to close a Q that is more detailed and, I think, better written, leaving a less informative and descriptive Q the canonical one. – Benji XVI Jan 07 '12 at 11:19
  • @Benji XVI: I'd still think that it would be better to not derive from std::priority_queue and implement the few functions using the STL algorithms for the actual logic. Also, I don't buy into the argument for the initial set up requiring access to the internal container: std::priority_queue can be constructed from a range. However, I had missed that std::make_heap() actually only takes O(n) time. – Dietmar Kühl Jan 07 '12 at 14:49
  • @DietmarKühl (Yeah, it’s a handy fact: creating a heap by sorting is faster than pushing values on one by one.) You make good points, but I can’t think of a single practical disadvantage to subclassing PQ. Can you give a specific way it “would be better” not to? (The upside is you don’t even need to write wrappers for `pop`, `push`, `empty`, `size`, & `top` – you get ’em all free.) – Benji XVI Jan 07 '12 at 17:29
  • @BenjibXVI: sure: std::unique_ptr>(new BinaryHeap()); looks like a reasonable thing to do (well, between construction and deallocation you'd probably do something) but causes undefined behavior. – Dietmar Kühl Jan 07 '12 at 19:41

1 Answers1

2

In the end its simply a matter of the standard defining such a behaviour for templates. The (C++11) standard says the following about non dependent name resolution in [temp.nondep] (14.6.3) :

Non-dependent names used in a template definition are found using the usual name lookup and bound at the point they are used.

Since c doesn't obviously depend on a template parameter the compiler treats it as a non-dependent name. So what happens is basically the following: When the compiler looks at BinaryHeap the actual type of notetype is unknown and therefore priority_queue<nodetype> is too, since it depends on nodetype (it could be a partial specialization). Therefore the compiler can't look into that type for the resolution (since we are talking about the point where the template is defined, not where it is instantiated). So it looks in the containing scopes for something called c, finding none and therefore rejecting the code. Using this->c (or using priority_queue<nodetype>::c) makes c a dependent name (since its not a member of BinaryHeap it must be a member of priority_queue<nodetype>), so the name lookup is delayed until the point of instantiation of the template, where notetype is known and the compiler can therefore search in priority_queue<nodetype>.

For your edit: yes, that is correct

Grizzly
  • 19,595
  • 4
  • 60
  • 78
  • *When the compiler looks at BinaryHeap the actual type of notetype is unknown* – My understanding is that templated classes are compiled on demand, once instantiated. Therefore the compiler does, definitely, know the type of nodetype. Indeed, multiple versions of a templated class/fn will be compiled, each time a new instantiation with a different type is encountered. – Benji XVI Jan 07 '12 at 01:54
  • @BenjiXVI: There are phases, though. Case in point: dependent types – Lightness Races in Orbit Jan 07 '12 at 02:12
  • @Benji XVI: As I mentioned in the end the reason is that this is the behaviour mandated by the standard. This allows the compiler to do some part of the compilation when encountering the template instead of when instantiating it. As Lightness Races in Orbit mentioned the compiler does the compilation in several phases. – Grizzly Jan 07 '12 at 02:52
  • @LightnessRacesinOrbit: yes, point taken. – Benji XVI Jan 07 '12 at 11:12
  • @Grizzly Thanks, makes sense! Maybe move “[It] allows the compiler to do some part of the compilation when encountering the template instead of when instantiating it” into the answer – it would seem to be! – Benji XVI Jan 07 '12 at 11:20