2

If I have a header like this:

#include <vector>

class Data;
class A {
  // const Data& getData( int i ) const { return a[i]; }
  std::vector<Data> a;
};

the compiler compiles it normally, because it doesn't need to know a bit about Data type.

Retuning a value by reference does not depend on a class implementation and therefore do not require any knowledge of the class internals. But when I uncomment the accessor compiler starts to complain on invalid use of incomplete type 'class Data'. Why?

klm123
  • 12,105
  • 14
  • 57
  • 95

4 Answers4

4

So, the C++ Standard is not particularly clear on this point. The problem that you are hitting is whether or not you can instantiate vector<Data> when you Data is not a complete type.

If we look at Section 17.6.4.8 Paragraph 2, we find the following statement:

In particular, the effects are undefined in the following cases:

  • ...
  • if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.

std::vector does not explicitly allow incomplete types for the template argument, so this is technically invalid code.


This would mean that this is invalid code, even if your compiler accepts it.

class Data;
class A {
    std::vector<Data> a;
};
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • i don't think the error is about `std::vector` , the error can be reproduced without std vector. – Raxvan Nov 25 '13 at 15:27
  • @Raxvan: I'm sorry, I don't understand your point. Could you elaborate? Note that the code that is accepted by the compiler (when the accessor is commented out) is also illegal, even if a compiler accepts it. – Bill Lynch Nov 25 '13 at 15:29
  • @sharth consider you have the type `Data` forward declared and then you have `const Data & foo(){return *some_poiner_to_data};` The following expression will give error: `foo();` whereas writing `const Data & tmp = foo();` is ok .. i think this is the question about, is it ? – Raxvan Nov 25 '13 at 15:35
  • 1
    The problem here is that you are confusing instantiating and declaring. Instantiating happens when the constructor of the class is called. When the type does not effect the size of the underlying container (such as with vector, but also with all smart pointers) you need to have the class defined before the constructor call. Here, implicit instantiation kicks your butt. – IdeaHat Nov 25 '13 at 15:47
  • @MadScienceDreams if you are referring to my little example then there is no constructor called in the expression `foo();` The error is however only in visual studio compiler, clang and gcc are sane and give no errors . – Raxvan Nov 25 '13 at 15:53
  • @Raxvan I'm not talking about whether or not the compiler catches the bug. I am just clarifying that not all declaration of template types require their template parameters to be defined at *declaration*, as you've implied with your example. – IdeaHat Nov 25 '13 at 15:58
  • @Raxvan: Just to clarify, the following code, which is what I believe you are talking about, should __not__ cause any compiler issues. http://ideone.com/mOxoMF (excluding the fact that I have dereferenced null). – Bill Lynch Nov 25 '13 at 15:59
  • @sharth yes it shouldn't, but it happens on my visual studio 2013 express installed. I checked it afterwards on clang and gcc and it's working fine. – Raxvan Nov 25 '13 at 18:17
3

Thanks to people reaction I understood the problem better. It becomes more clear when we rid from std::vector:

class Data;
class A {
  const Data& getData( int i ) const { return a[i]; }
  Data* a;
};

to return i-th element of array a one needs to know where is it in the memory. For this one needs to know sizeof(Data), which is not possible for incomplete type.

The exactly same problem std::vector should have.

klm123
  • 12,105
  • 14
  • 57
  • 95
1

Because knowing that there exists a class Data is not enough to know what std::vector<Data>::operator[](size_t i) should do, or even whether it exists. This is fundamental to the nature of class templates.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • std::vector::operator[](size_t i) doesn't exist for some types T? For example? – klm123 Nov 25 '13 at 15:48
  • @klm123: It probably does (for all _compatible_ types, at least), but the compiler doesn't know that until it has instantiated the template and resolved its arguments. There could easily be a specialisation for `std::vector` that has no members at all, and though the standard makes this UB in the case of standard containers (I assume), that doesn't change the fundamental behaviour of class templates. – Lightness Races in Orbit Nov 25 '13 at 15:49
  • Then it should not depend on whether operator[] is used or not. – klm123 Nov 25 '13 at 15:55
  • @klm123: Why's that then? There are no other references to members of `std::vector` here. Quite clearly, without any references to members, the code can compile (though it's still illegal, technically). – Lightness Races in Orbit Nov 25 '13 at 15:56
  • Ok. I get it. Thank you. – klm123 Nov 25 '13 at 16:53
1

It does not require a complete type in the declaration. It requires a complete type before the vector constructor and destructor (or any other members really) are called. This is when the size of the struct or class needs to be defined.

In your code, the A's constructor and Destructor are declared as default, and thus are defined in the header file. The vector cannot allocate or deallocate stuff of a size it does not know, so it barfs. You can solve this by explicitly declaring your destructor and constructor in "A.h", and making sure Data is defined before any of your member functions are defined.

The following code works and is valid.

//"A.h"
class Data; // forward declaration only
class A
{
public:
  A();
  ~A();
  const Data& getData( int i ) const;
  std::vector<Data> a;
};

//"A.cpp"
#include "A.h"
class Data {int i;}; // actual class here before the definition of A::A(),...

A::A(){}
A::~A(){}
Data& A::getData(int i) const {return a[i];}
stefan
  • 10,215
  • 4
  • 49
  • 90
IdeaHat
  • 7,641
  • 1
  • 22
  • 53