2

Why can't I do something like this in C++?

A.h:

#ifndef A_H
#define A_H
#include "B.h"

struct A {
    int a;
};

void doStuff1 (B b);  // Error here

#endif

B.h:

#ifndef B_H
#define B_H
#include "A.h"

struct B {
    int b;
};

void doStuff2 (A a);  // Error here

#endif

I get an error that 'A' was not declared in this scope and the same with 'B'. I know about forward declaration, but I wanted to see if it was possible to have a set up like this as pass-by-value instead of by reference/pointer. Why does the compiler behave like this, if both A and B are in fact declared by the time the compiler reaches that code?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
FuzzyCat444
  • 315
  • 2
  • 7
  • If you really need to, put function declarations in a separate header. – iBug Oct 03 '18 at 02:45
  • @iBug Right, but why does this not work? – FuzzyCat444 Oct 03 '18 at 02:46
  • 1
    There's likely a duplicate somewhere the explains this, but including a header is essentially just copying+pasting the contents of the header in the file. Once you do that you realise why the compiler can't resolve this. Including "a" begins to include "b" but to include "b" we don't know what an `A` is so it errors. – Tas Oct 03 '18 at 02:46
  • Not sure if duplicate but the answer does explain (better) what I was trying to say: https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes – Tas Oct 03 '18 at 02:47
  • @Tas Isn't that just suggesting forward declaration? It doesn't make sense to me why you would absolutely have to do that in this case. – FuzzyCat444 Oct 03 '18 at 02:49
  • @FuzzyCat444 the same reason for forward declaration. the compiler don't look forward for you. – apple apple Oct 03 '18 at 02:53
  • @appleapple But in A.h it should look inside B.h when it sees `#include "B.h"` right? And then it knows what `B` is and its size? – FuzzyCat444 Oct 03 '18 at 02:55
  • 1
    @FuzzyCat444 same as @​Tas say. `#include "B.h"` appends exactly the position of `#include "B.h"`, no further processing of `A.h` is done before it is completed. – apple apple Oct 03 '18 at 02:59
  • @appleapple So does it just get stuck in a circular dependency loop? What goes wrong? – FuzzyCat444 Oct 03 '18 at 03:01
  • Fuzzycat you just temper tantrum that you don't want to use order in your design? – Öö Tiib Oct 03 '18 at 03:01
  • @appleapple Nevermind! I get it I think – FuzzyCat444 Oct 03 '18 at 03:01
  • @ÖöTiib I just want to learn. I have never structured code like this. – FuzzyCat444 Oct 03 '18 at 03:02
  • @FuzzyCat444 that's good :) – apple apple Oct 03 '18 at 03:02

2 Answers2

2

The basic lesson: Includes are processed before any C++ is parsed. They're handled by the pre-compiler.

Let's say that A.h winds up being included prior to B.h. You get something like this:

#ifndef A_H
#define A_H

// ----- B.h include -----    

#ifndef B_H
#define B_H
#include "A.h" // A_H is defined, so this does nothing

struct B {
    int b;
};

void doStuff2 (A a);  // Error here

#endif

// ----- B.h include -----

struct A {
    int a;
};

void doStuff1 (B b);  // Error here

#endif

At this point, the C++ compiler can take over and start parsing things out. It will try to figure out what the parameter to doStuff2 is, but A hasn't been defined yet. The same logic holds true going the other way. In both cases, you have dependencies on types that haven't been defined yet.

All of this just means that you have your dependencies out of order. It isn't a problem with pass-by-value. Your types must be defined prior to your methods. That's all - see the example below.

// Example program
#include <iostream>
#include <string>

// data_types.h
struct A
{
    int x;
};

struct B
{
    int y;
};

using namespace std;
// methods_A.h
void foo(A a)
{
    a.x = 3;
    cout << "a: " << a.x << endl;
}

// methods_B.h
void bar(B b)
{
    b.y = 4;
    cout << "b: " << b.y << endl;
}

int main()
{
   A first;
   B second;
   first.x = 0;
   second.y = 100;
   foo(first);
   bar(second);
   cout << "A: " << first.x << ", B: " << second.y << endl;
}

Example output

a: 3
b: 4
A: 0, B: 100
Will Bickford
  • 5,381
  • 2
  • 30
  • 45
  • That makes sense! But in the case where the structs had to be in separate files with the functions, there would be no workaround right? – FuzzyCat444 Oct 03 '18 at 03:06
  • 1
    Right. You'll either need to separate the includes or use const references and avoid passing by value so you can make use of forward declarations. You'd have to put the implementation in your cpp files too. – Will Bickford Oct 03 '18 at 03:08
1

You have a circular include. You need to either separate them into different header files, such as having A.h and B.h only declare the struct/classes and having a different header file declare the functions.

The problem can also be solved by using forward declarations and passing by reference instead:

struct A;
struct B;

void doStuff1(A& a);
void doStuff2(B& b);
Kristopher Ives
  • 5,838
  • 7
  • 42
  • 67