2

In C++11, it is definitely invalid to declare a vector of a class which is still an incomplete type right? I thought I could only use incomplete types as pointers, references, return types or parameter types. Searching (1) for "vector of incomplete type" suggests to me that containers of incomplete types should be an error (I'm using g++ version 4.8.1.). However the following code compiles fine on my system:

#ifndef SCREEN
#define SCREEN
#include <string>

using namespace std;

class Screen;

class Window_mgr{
public:
    typedef vector<Screen>::size_type screenindex;
    void clear(screenindex);
private:
    vector<Screen> screens;
};

class Screen{
    friend void Window_mgr::clear(screenindex);
public:
    typedef string::size_type pos;
    Screen() = default;
    Screen(pos ht, pos wt): height(ht), width(wt), contents(ht*wt, ' ') { }
    Screen(pos ht, pos wt, char c): height(ht), width(wt), contents(ht*wt, c) { }

private:
    pos height = 0, width = 0;
    pos cursor = 0;
    string contents;

};

inline void Window_mgr::clear(screenindex i){
    Screen& s = screens[i];
    s.contents = string(s.height*s.width, ' ');
}

#endif // SCREEN

despite the fact Window_mgr declares a vector of Screens, which is still an incomplete type. These classes in my example are actually based on Chapter 7.3 of C++ Primer for any that have it. A question asked me to define my own versions of Screen and Window_mgr in which the member function clear is a member of Window_mgr and a friend of Screen. Except Window_mgr is also meant to contain a vector of Screens.

If creating a vector of an incomplete type IS invalid, how exactly would I do this using forward declarations? If I have a vector of screens in Window_mgr then its class definition must come after class Screen has already been defined. Except Screen must have a friend declaration of the clear member function of Window_mgr, but the following re-arrangement is an error because Screen uses the scope operator on an incomplete type;

class Window_mgr;

class Screen{
    friend void Window_mgr::clear(screenindex);
public:
    typedef string::size_type pos;
    Screen() = default;
    Screen(pos ht, pos wt): height(ht), width(wt), contents(ht*wt, ' ') { }
    Screen(pos ht, pos wt, char c): height(ht), width(wt), contents(ht*wt, c) { }

private:
    pos height = 0, width = 0;
    pos cursor = 0;
    string contents;

};

class Window_mgr{
public:
    typedef vector<Screen>::size_type screenindex;
    void clear(screenindex);
private:
    vector<Screen> screens;
};

inline void Window_mgr::clear(screenindex i){
    Screen& s = screens[i];
    s.contents = string(s.height*s.width, ' ');
}

The only way I could think would be to replace the memberfunction friend declaration with a just friend declaration of the class

class Screen{
friend class Window_mgr;
// other stuff
}

but that's not what the question wants of me.

Community
  • 1
  • 1
AntiElephant
  • 1,227
  • 10
  • 18
  • _"... If creating a vector of an incomplete type IS invalid ..."_ It isn't. – πάντα ῥεῖ Aug 06 '15 at 14:53
  • It is *undefined behaviour* to instantiate containers of incomplete types. The implementation doesn't have to produce an error. But, boost has some [containers of incomplete types](http://www.boost.org/doc/libs/1_58_0/doc/html/container/main_features.html#container.main_features.containers_of_incomplete_types) – juanchopanza Aug 06 '15 at 14:57
  • @πάνταῥεῖ: It isn't valid or it isn't invalid? Sorry, I'm just not sure how to parse the double negatives there. – Cornstalks Aug 06 '15 at 14:58
  • @Cornstalks It isn't invalid (referred to OP's emphasized _IS_). – πάντα ῥεῖ Aug 06 '15 at 15:01
  • 1
    Is this perhaps part of [two-phase lookup](http://stackoverflow.com/questions/7767626/two-phase-lookup-explanation-needed) for templates? I'm not sure what's defined as "instantiation" for the second phase, and so I'm curious if perhaps `vector screens;` (inside `Window_mgr`'s definition) is considered an instantiation, or if perhaps instantiation is only considered when the `Window_mgr` type is used later. – Cornstalks Aug 06 '15 at 15:01
  • @πάνταῥεῖ It **IS** invalid. Well, it is undefined behaviour. – juanchopanza Aug 06 '15 at 15:05

2 Answers2

4

Incomplete types are currently not supported by standard containers; cf. Can standard container templates be instantiated with incomplete types? - that said, implementations can choose to support incomplete types as an extension, but that makes your code non-portable.

The paper n4371 Minimal incomplete type support for standard containers, revision 2 has been incorporated into the most recent draft (n4527) of the C++ Standard, so barring the unexpected it is very likely that incomplete types will be supported for vector, list and forward_list in C++17.


There is one way to satisfy the requirements in "C++ Primer" without depending on implementation extension or C++17:

  1. clear is a member function of Window_mgr;
  2. Window_mgr::clear() is a friend of Screen;
  3. Window_mgr is contains a vector of Screens:

You could make Screen a nested class of Window_mgr:

class Window_mgr{
public:
    typedef std::size_t screenindex;
    void clear(screenindex);

    class Screen{
        friend void Window_mgr::clear(screenindex);
    public:
        // ...
    };

// ...
private:
    vector<Screen> screens;
};

Even here I've had to adjust the definition of type screenindex to break the dependency cycle.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
3

The short answer is, it is illegal to use vector with an incomplete type. Just like it would be with any other STL container. The fact that something compiles and might even work flawlessly, doesn't mean it's legal.

The article you mention in (1) references this article. Which gives a very clear (albeit it quite long) explanation of the matter.

As I interpret this it comes down to the following:

  • In general using an STL template on an incomplete type is illegal
  • In practice it works with some STL containers and libraries
  • Vector is the one case where you can actually feel safe using it. The last bit of the notes chapter of the article I mentioned states:

But perhaps it should be relaxed on a case-by-case basis, and vector looks like a good candidate for such special-case treatment: it's the one standard container class where there are good reasons to instantiate it with an incomplete type and where Standard Library implementors want to make it work.

The other problem you describe is a common one. To simplify it you could say the following. Say you have two classes:

class Foo
{
   std::vector<Bar> myVector;
}

class Bar
{
   std::vector<Foo> myVector;
}

This seems like a reasonable thing to want. But within the restrictions of the standard it's simple impossible. A way to solve this according to the standard is using a list of pointers(std::shared_pointer is useful here). Or, resign to not following the standard and use forward declaration. With almost any compiler this will work.

Community
  • 1
  • 1
laurisvr
  • 2,724
  • 6
  • 25
  • 44