Student and class does not name a type

Pages: 12
mbozzi wrote:
Line 9 technically has undefined behavior before C++17, although mainstream implementations supported this for std::vector, std::list, and std::forward_list via library extension.
Oh, I didn't actually know that wasn't necessarily guaranteed to work before C++17. I suppose it's because particular libraries could do small-size optimization or something of that nature.

mbozzi wrote:
t remains undefined behavior to instantiate any other container with an incomplete type.
Undefined behavior, though? How could it even compile if there isn't a complete type when one is required?

adam2016 wrote:
I thought compilation creates object code but not one line at a time like an interpreter, so shouldn't the compiler not be able to realise that class B was declared albeit after class A?
C++ has separate compilation. The question the compiler needs to ask is "can I create an object file from this source file [technically, translation unit]". So Student will become its own object file when you compile student.cpp, Classroom will become its own object file when you compile classroom.cpp, main will become its own object file when you compile main.cpp. [Note: Don't confusing "object file" with the OOP notion of an "object" -- two separate things, unfortunate names]

If you're only declaring a pointer to a class, the compiler doesn't need to know the full definition to be able to compile that file. If you are declaring an object of that class, or using a member of that class, then the compiler needs to know the definition of the class. Otherwise, it just needs to know that it is a class.

adam2016 wrote:
why can you use Student objects in a container?
You can't use Student objects in a container if Student hasn't been defined yet. You can still declare the vector though, because a vector, under the hood, will just look (something like) this:
1
2
3
4
5
6
class vector_of_some_type {
    some_type* pointer_to_dynamic_array;
    size_t size;
    size_t capacity;
    // etc.
};
(As mbozzi said, this is only guaranteed for std::vector after C++17, which I just learned)

___________________________________

In other words:
1
2
3
Foo foo; // requires Foo to be defined
Foo* pFoo; // only requires the compiler to know that Foo is a class
pFoo = new Foo(); // requires Foo to be defined 


1
2
3
4
void func() {
    vector<Foo> foos; // requires only the compiler to know Foo is a class
    vector<Foo> foos(42); // requires definition of Foo [makes 42 objects]
}


___________________________________

It's not specifically about using header files. You can logically combine everything into one file if you chose to to.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Example program
#include <vector>

class Student;

class Classroom {
  public:
    std::vector<Student> students;
    const Student& get_student();
};


class Student {
  public:
    Classroom* pClassroom = nullptr;
};

const Student& Classroom::get_student()
{
    return students[0];
}

int main()
{
    Classroom classroom;
    {
        Student student;
        classroom.students = { student }; // copying Student into classroom.students
    }
    const Student& the_student = classroom.get_student();
}
Last edited on
probably beating a dead horse here but

This works at all because the member functions of a class template are not instantiated (i.e., written out by the compiler) until those member functions are used in the program. While most member functions require that B is complete, the class body alone does not (assuming the allocator type meets some basic requirements).


In my example you can't declare a B object in class A because the compiler needs to know how much space A will take up right?

but when I put B as a template argument it allows this? You already gave the answer but still don't fully understand, apologies in advance.

It has nothing to do with templates specifically, templates just add a layer of complexity to this discussion (or perhaps, hide a layer of complexity).

This is what happens when you make a vector<Student>... very roughly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <cstddef> // size_t

class Student;

class vector_Student {
  public:
    vector_Student();
    Student* students_arr;
    size_t size;
    // truncated for brevity of example
};

class Classroom;

class Student {
  public:
    Classroom* pClassroom = nullptr;
};

vector_Student::vector_Student()
 : students_arr(nullptr), size(0)
 { }


class Classroom {
  public:
    vector_Student students;
    const Student& get_student();
};

const Student& Classroom::get_student()
{
    return students.students_arr[0];
}

int main()
{
    Classroom classroom;
    {
        Student student;
        classroom.students.students_arr = new Student[1] { student }; // copying Student into classroom.students_arr
    }
    const Student& the_student = classroom.get_student();
}
Last edited on
Ahh ok that makes perfect sense now :)

1
2
3
4
5
6

void func() {
    vector<Foo> foos; // requires only the compiler to know Foo is a class
    vector<Foo> foos(42); // requires definition of Foo [makes 42 objects]
}


so behind the scenes Vector<Foo> foos will just declare a vector<Foo>? but won't the default constructor for vector create lets say an array of 10 ( or some fixed amount ) of Foos? or is this done when we call the push_back function, in other words an array of Foos has not been instantiated until we call the push_back() function?

1
2
3
4
5

vector_Student::vector_Student()
 : students_arr(nullptr), size(0)
 { }


so vector must allocate space only when a Foo object is pushed onto the array.



Thanks
Last edited on
but won't the default constructor for vector create lets say an array of 10 ( or some fixed amount ) of Foos?
If the default constructor did do that, then you simply wouldn't be able to call the constructor until the full definition of Foo was known.
But it happens to not do that (as mbozzi said, this is apparently guaranteed in C++17 but I imagine ubiquitous since C++11 or even before).

push_back will copy (or move, if applicable) a created object into the vector's array, so yes the full definition is needed at that point because you're now dealing with an object that exists.

Even if std::vector's default constructor did create one or more Foo objects, it isn't the end of the world. It just means that the full definition of Foo has to be known before the constructor is called.
Last edited on
makes sense :) Thanks
Topic archived. No new replies allowed.
Pages: 12