1

I have the following code:

foo.h

#ifndef FOO_H
#define FOO_H

#include "bar.h"

class Foo
{
public:
    Foo(Bar bar);
};
#endif //FOO_H

bar.h

#ifndef BAR_H
#define BAR_H

#include "foo.h"

class Bar
{
public:
    Bar(Foo foo);
};
#endif //BAR_H

If I compile that, I get the following error message:

expected ')' before 'foo'        bar.h      line 9

After looking on this website, I fixed it by using a forward declaration of Foo in bar.h, and Bar in foo.h.

My question is, why does the compiler make this error sound like a syntax error, whilst it's actually not ? I would think that catching such an error and return a proper error message would be quite simple.

  • 1
    `#include bar.h` and `#include foo.h` are incorrect. Those file names should be quoted. – cHao Jan 15 '18 at 19:04
  • 5
    When encountering an error like this, a compiler has to take an educated guess at what the programmer intended. It doesn't always choose wisely. – lurker Jan 15 '18 at 19:05
  • 1
    This *is* a syntax error. Depending which of the two headers you include first, there is no type `Foo` or type `Boo` when you first use it. See https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes – François Andrieux Jan 15 '18 at 19:07
  • @cHao sorry, not the same computer, I fixed it (they are quoted in my files) –  Jan 15 '18 at 19:09
  • 1
    Try several compilers (e.g. [GCC](http://gcc.gnu.org/) & [Clang](http://clang.llvm.org/)...) – Basile Starynkevitch Jan 15 '18 at 19:10
  • @FrançoisAndrieux I thought there would be some kind of specialized error, like `Unknown type error` or something –  Jan 15 '18 at 19:17
  • @Insomgla that's a possible error, but in cases like this, from a language syntax perspective, there are other possible options. So the compiler picks one it thinks is reasonable. – lurker Jan 15 '18 at 19:24

2 Answers2

3

You have headers with unresolved circular dependency. That is when your code somewhere includes "foo.h" first then after preprocessing it will become

class Bar // expanded from #include "bar.h"
{
public:
    Bar(Foo foo); // Foo is not declared at this point
};

class Foo // rest of foo.h content
{
public:
    Foo(Bar bar);
};

if your code includes "bar.h" first then after preprocessing it will become

class Foo // expanded from #include "foo.h"
{
public:
    Foo(Bar bar); // Bar is not declared at this point
};

class Bar // rest of bar.h content
{
public:
    Bar(Foo foo);
};

So there is an error in both cases.

To get around this issue you need to utilize proper forward declarations:

// foo.fwd.h
#ifndef FOO_FWD_H
#define FOO_FWD_H

class Foo;

#endif // FOO_FWD_H

// bar.fwd.h
#ifndef BAR_FWD_H
#define BAR_FWD_H

class Bar;

#endif // BAR_FWD_H

and include them into headers instead of header with complete class declaration:

// foo.h
#ifndef FOO_H
#define FOO_H

#include "bar.fwd.h"

class Foo
{
public:
  Foo(Bar bar);
};
#endif //FOO_H

// bar.h
#ifndef BAR_H
#define BAR_H

#include "foo.fwd.h"

class Bar
{
public:
  Bar(Foo foo);
};
#endif //BAR_H

and then include headers with class definition only into .cpp or implementation file.

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • Thanks for your input. However, my question was more about why does C++ does have such an unclear error message ? Why not a simple message stating that type of the object is not known ? –  Jan 15 '18 at 19:21
  • 1
    @Insomgla C++ suffers from inconsistent syntax with high complexity that been clogging for ~30 years. And it's getting worse with every new standard. For example there are like 7 different ways to initialize a variable, yet standard committee adds 8th one instead of deprecating / removing at least 4 of redundant existing ones. It is a miracle that compilers still somehow manage to at least show potential erroneous spot. – user7860670 Jan 15 '18 at 19:28
  • 2
    @Insomgla - C++ itself doesn't define any error messages. The error text is up to the compiler vendor. Different compilers give messages with varying degrees of quality. The error text is not something C++ itself is defined to have. – StoryTeller - Unslander Monica Jan 15 '18 at 19:29
  • @StoryTeller Yep sorry, I typed too fast. As stated in my question, I meant the compiler. I'll try different compilers to see if I get something a bit clearer. –  Jan 15 '18 at 19:41
  • @Insomgla you may find that using multiple compilers is very helpful beyond readable error messages. A logic error that does not show up (usually because the undefined behaviour invoked by the code is implemented by the compiler in a manner similar to what you expected) in compiler A is revealed by the different decisions made by compiler B. – user4581301 Jan 15 '18 at 19:58
1

C++ is very hard to parse. When compiler does not know that Foo is name of some type then it expects that we try to declare members with that name in Bar. Code does not parse as any of valid variants of member declarations.

Old compilers just diagnosed such cases as "syntax error". Modern compilers try to be friendlier. The diagnostic likely tries to help us to correct the code towards one of such (or some other similar) valid member declaration.

class Bar
{
public:
    Bar (Foo());
    Bar (*Moo);
    Bar Roo();    
};

Unfortunately it guessed totally wrongly since Foo was not meant as member name but as a type of parameter of constructor.

Öö Tiib
  • 10,809
  • 25
  • 44