1

I'm having a bit of trouble with a method I'm trying to write for a class. I have class symbol and class terminal. class terminal extends class symbol, but one of the methods of class symbol needs to return a vector. E.g.:

#ifndef SYMBOL_H
#define SYMBOL_H

#include "terminal.h"
#include <vector>

using namespace std;

class symbol {
   public:
        vector<terminal> first();
        virtual void polymorphable();
};

#endif

With class terminal defined:

#ifndef TERMINAL_H
#define TERMINAL_H

#include "symbol.h"

using namespace std;

class terminal: public symbol {
    // ...
};

#endif

However, when doing this, I get two errors when building, with one or the other coming first: "'terminal' : undeclared identifier" on the line that defines the vector function, and "'symbol' : base class undefined" on the line with the terminal class definition.

How do I solve this 'a requires b', 'b requires a' issue?

Casey Kuball
  • 7,717
  • 5
  • 38
  • 70

7 Answers7

3

Avoid circular dependencies by using Forward Declarations.

class terminal;

class symbol
{
  std::vector<terminal> first();
  // ...
};

There is a speculation of this approach being Undefined as per C++ standard.
@Ben Voight points out:

C++03 standard Section 17.6.4.8 says:

"In particular, the effects are undefined in the following cases: ... if an incomplete type is used as a template argument when instantiating a template component, unless specifically allowed for that component...

Whether std::vector<X> f(); is std::vector<X> an instantiation, is being discussed here. If the answer there proves it is then this answer holds no good and I will delete the same, or else this stays valid.

Community
  • 1
  • 1
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • 1
    http://www.adp-gmbh.ch/cpp/forward_decl.html just a good reference since an example isn't provided and most show it with pointers instead of concrete objects :) – John Humphreys Oct 11 '11 at 17:51
  • @JamesMcNellis: How does it cause *instantiation* of `std::vector` when it is only a return type in a function declaration? Care to explain, please. – Alok Save Oct 11 '11 at 18:27
2

Edit: The following may not be allowed by the standard (see the comments). In that case, you simply cannot have a proper circular dependency: If the size of A depends on the size of a member of type B, but the size of B depends on the size of a member of type A, then such a definition simply doesn't make sense.

I'm not entirely certain if that applies to your situation, though, since you only declare a function whose return type is incomplete, which is allowed. See the attendant question by James; hopefully we'll get a definite answer there.


Just forward-declare terminal:

class terminal;

class symbol
{
  std::vector<terminal> first();
  // ...
};

You can forward-declare anything that only needs to be an incomplete type. Incomplete types can be used to form pointers, references, function signatures. The complete type only needs to be known when variables of that type are used.

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I don't think using an incomplete type as the template argument to `std::vector` is correct. – Ben Voigt Oct 11 '11 at 17:53
  • Are you sure? I just tried it and it works, but I'm not entirely sure about the standard on that... `vector` only uses a `T*`, non? – Kerrek SB Oct 11 '11 at 17:54
  • @KerrekSB: No, `vector` also needs to call the constructors and destructor of `T`. It's **undefined behavior**, so it may appear to work, but it isn't **correct**. – Ben Voigt Oct 11 '11 at 17:54
  • @Ben: Only when you instantiate the `symbol` class. We're only defining it here. – Kerrek SB Oct 11 '11 at 17:55
  • Also note that we're only declaring a function, and function argument and return types can be incomplete. – Kerrek SB Oct 11 '11 at 17:58
  • @BenVoigt: An quote from the standard shall end the confusion on this, so if you can please provide that. – Alok Save Oct 11 '11 at 17:58
  • I'm pretty sure Kerrek is right... its separation of declaration from implementation. I'm pretty sure I read past this exact subject a few days ago in Effective C++ III and it stated you could happily do this. I'll pull an example out in a while when I'm near my book to double check though. – John Humphreys Oct 11 '11 at 17:58
  • @w00te: I'm pretty sure Kerrek is wrong. `symbol` doesn't have a user-defined destructor, so the destructor is generated using only the definitions in scope for the class body (practically speaking) and will fail to correctly destroy the vector. I'll find the standardese if James doesn't beat me to it. – Ben Voigt Oct 11 '11 at 18:01
  • I'm very happy to be proven wrong. I just thought that member function signatures don't require complete types. If this is totally wrong I'll delete the answer. – Kerrek SB Oct 11 '11 at 18:02
  • I'll admit that I've probably got the least knowledge in this chat (especailly about compilation processes), but I was pretty sure that what I read agreed with Kerrek's most recent comment. Like I said, I'll double check and add a last post later out of curiosity though since it's such a big debate and I'm honestly not sure :) – John Humphreys Oct 11 '11 at 18:04
  • @BenVoigt: I second What Kerrek SB, said & only way to prove that is to quote the holy book, which I already requested you to, So if you can, please do so. – Alok Save Oct 11 '11 at 18:04
  • @Als: Section 17.6.4.8 says "In particular, the effects are undefined in the following cases: ... if an incomplete type is used as a template argument when instantiating a template component, unless specifically allowed for that component." In the standard library, only `unique_ptr`, `default_delete` (with restrictions), `weak_ptr`, and `shared_ptr`, and `enable_shared_from_this` allow incomplete types. – Ben Voigt Oct 11 '11 at 18:05
  • @BenVoigt: In this case it is the return type of the function which is the Incomplete type, Why does the compiler need to know the layout of this type? – Alok Save Oct 11 '11 at 18:06
  • @Als: You've used an incomplete type as a template argument, and `std::vector` does not give you express permission to do so. Therefore, per 17.6.4.8, behavior is undefined. – Ben Voigt Oct 11 '11 at 18:10
  • @BenVoigt: So the question is, are we "instantiating a template component"? – Kerrek SB Oct 11 '11 at 18:23
  • Well, I do not know the answer with certainty. So, I've asked: [In the declaration “std::vector f();”, is “std::vector” an instantiation?](http://stackoverflow.com/questions/7730874/in-the-declaration-stdvectorx-f-is-stdvectorx-an-instantiation) – James McNellis Oct 11 '11 at 18:45
  • @JamesMcNellis: Thanks, that's probably the best course of action! We can bounty that if necessary. – Kerrek SB Oct 11 '11 at 19:12
  • Effective C++ 3rd Edition by Scott Meyers, item #31 "Minimize compilation dependencies between files", page 143: "Note that you never need a class definition to declare a function using the class, not even if the function passes or returns the class by value. {class Date; Date Today(); void clearAppointments(Date D); } is fine, no definition of date is needed". So, i think no definition of date is needed until the file where the function implementation is provided. This should apply equally to the question here I believe. – John Humphreys Oct 11 '11 at 19:20
  • @w00te: Thanks - that's exactly what I expect. Let's hope that the standard agrees, though, and note that the discussion is a little bit different, as it is about an additional clause about the standard library containers, and whether appearing as a function return type constitutes "template instantiation". – Kerrek SB Oct 11 '11 at 19:23
  • @w00te: You can actually quote the same on the Q, James posted, In my understanding this does not cause `template instantiation`. Thanks for digging that out though. – Alok Save Oct 11 '11 at 19:30
1

Base class should not need to know anything about Derived class. That's an important principle in Object Oriented Design. You could potentially change the base class to be:

class symbol {
  public:
    vector<symbol*> first();
    virtual void polymorphable();
};

Now, first() returns a vector of pointers to the base class. With polymorphism, each pointer can actually point to the derived class. Note that I changed it to use pointers. If you changed it to simply vector<symbol> that wouldn't work.

Alternatly, if you really need the Base class to know about the existence of the Derived class, you can forward declare the derived class:

#ifndef SYMBOL_H
#define SYMBOL_H

#include "terminal.h"
#include <vector>

using namespace std;
class terminal;  // forward declare the existence of this class

class symbol {
  public:
    vector<terminal*> first();     // change to be a vector of pointers
                                   // to avoid issues with incomplete type
    virtual void polymorphable();
};

#endif
Tim
  • 8,912
  • 3
  • 39
  • 57
1

Use forward declarations.

James - note declarations differ from instantion. This code works fine

#include <vector>

class terminal; <--- TELLING THE COMPILER MAY USE terminal in the future.

class symbol 
{        
  std::vector<terminal> first(); <--- NOTE THE COMPILER DOES NOT NEED TO KNOW HOW TO CONSTRUCT EITHER
  // ... 
};           

class terminal: public symbol  < --- TELLS COMPILER THAT terminal INHERITS symbol i.e. CONTAINING THE METHOD first
{
   int wibble; 
};  

int main()
{
    symbol s;
    return 0;
}

Als - You are correct.

Ed Heal
  • 59,252
  • 17
  • 87
  • 127
  • Section 17.6.4.8 says you're wrong. Or can you cite from the standard where `std::vector` explicitly allows using an incomplete type? – Ben Voigt Oct 11 '11 at 18:11
  • I have compiled it using `g++` (4.3.1). The `first` function declaration just says that it will return a `vector – Ed Heal Oct 11 '11 at 18:24
  • The fact that code compiles without error using a particular compiler with particular settings does not mean that the code is correct. I think `std::vector` here causes instantiation of the `std::vector` class template with an incomplete type, which causes the program to exhibit undefined behavior (I can't find normative text saying that a function declaration does not cause instantiation; I may be wrong). – James McNellis Oct 11 '11 at 18:27
  • James et al. Just try it. You are getting confused between declarations and implementations. I did not need any special switches to get it to compile. – Ed Heal Oct 11 '11 at 18:30
  • 1
    @EdHeal: "Compiles" != "well-defined behavior". – Ben Voigt Oct 11 '11 at 19:03
0

I think Curiously Recurring Template Pattern can get you out of this one:

template<typename terminal_type>
class symbol_pattern
{
   public:
        std::vector<terminal_type> first();
        virtual void polymorphable();
};

class terminal : public symbol_pattern<terminal>
{
};

typedef symbol_pattern<terminal> symbol;
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 3
    A little bit of an overkill, don't you think? – Kerrek SB Oct 11 '11 at 17:51
  • @KerrekSB, no I think your answer is a bit of underkill. – Mark Ransom Oct 11 '11 at 17:55
  • That might sound harsh, but very few people will have read about template metaprogramming enough to have come across that design pattern. It just confuses the next person who has to maintain it - especailly after a few people add to it down the line :( – John Humphreys Oct 11 '11 at 17:56
  • Meh my bad then, I know the pattern is for achieving static polymorphism at compile time rather than dynamic polymorphism at run time, I guess I made a bad logical leap. I still think it's a pretty complex solution to a common problem though. Maybe it's just that I don't understand it fully though :) – John Humphreys Oct 11 '11 at 18:17
  • 2
    Doesn't this have the same issue as the other examples that use a forward-declaration of `termimal`? When `symbol_pattern` is instantiated as the base class of `terminal`, `terminal` is still incomplete. So, `terminal_type` (which is `terminal`) is incomplete when it is used to instantiate `std::vector`. – James McNellis Oct 11 '11 at 18:23
  • @JamesMcNellis: I hadn't considered that. In any case, this prevents using `symbol` before `terminal` is completely defined, which is a real (as opposed to language-lawyerese) risk with a simple forward declaration. – Ben Voigt Oct 11 '11 at 19:05
0

Forward declaration + smart pointer (although this will now store things on the heap instead of stack... could be undesirable)

#ifndef SYMBOL_H
#define SYMBOL_H

#include <vector>
#include <memory>

using namespace std;

class terminal; // Make a forward declaration like this

class symbol {
   public:
        vector<shared_ptr<terminal>> first();
        virtual void polymorphable();
};

#endif
Michael Price
  • 8,088
  • 1
  • 17
  • 24
0

Rather than having a single member function return a container, consider having begin() and end() functions that return an iterator range, similar to what the Standard Library containers do themselves:

class terminal;

class terminal_iterator { /* defined appropriately */

struct symbol
{
    terminal_iterator begin_terminals() const;
    terminal_iterator end_terminals() const;
};

If you already have a std::vector<terminal> somewhere that you are going to be iterating over, you can just typedef terminal const* terminal_iterator; (or use a similar typedef) and define the member functions accordingly.

If you don't have a container (i.e., the member function would materialize the sequence itself), you can consider writing your own iterator class that generates the sequence.

Providing begin() and end() range accessors is sometimes a bit more work than simply providing an accessor for a container, but range accessors make for greater flexibility and abstraction.

James McNellis
  • 348,265
  • 75
  • 913
  • 977