nyronium
nyronium

Reputation: 1278

Why is the declaration/definition order still important in C++?

For many times now, I have had problems with the declaration and definition order in C++:

struct A {
    void Test() { B(); }
};

void B() {
    A a;
}

Of course this can be solved by predeclaring B(). Usually this is good enough to solve any of these problems. But when working with module based header-only libraries or similarily complex include systems, this declaration/definition concept can be really painful. I have included a simple example below.

Nowadays most modern language compilers do a two-pass over the source files to build the declarations in the first pass and process the definitions in the second one. Introducing this scheme into C++ shouldn't break any old code either. Therefore,


Example

This is an example of a module based header library, which has blocking includes because of missing predeclarations. To solve this, the user of the library would have to predeclare the "missing" classes, which is not feasible. Of course this problem might be solved by using a common include header that orders all declarations before definitions, but with a two-pass this code would also work, no modification required.

oom.h

#pragma once
#include "string.h"

struct OOM {
    String message;
};

string.h

#pragma once
#include "array.h"

struct String {
    Array data;
};

array.h

#pragma once

struct Array {
    void Alloc();
};

#include "oom.h"

void Array::Alloc() { throw OOM(); }

str_usage.cpp

#include "string.h"
int main() {
    String str;
}

Upvotes: 0

Views: 172

Answers (3)

T.C.
T.C.

Reputation: 137325

void f(int);
void g() { f(3.14); }
void f(double); 

g currently calls f(int), because it's the only f visible. What does it call in your world?

  • If it calls f(double), you just broke copious existing code.
  • If you came up with some rules to make it still call f(int), then that means if I write

    void g2() { f2(3.14); }
    void f2(double);
    

    and then introduce a worse match for the argument - say, void f2(int); before g2, g2 will suddenly start calling the wrong thing. That's a maintainability nightmare.

Upvotes: 2

Pete Becker
Pete Becker

Reputation: 76360

A much simpler solution is to separate class definitions from function definitions:

struct A {
    void Test();
};

struct B {
    A a;
};

inline void A::Test() {
    B();
}

Upvotes: 1

interjay
interjay

Reputation: 110148

There are ambiguities in the C++ grammar that can only be resolved if you know what an identifier refers to.

For example:

a * b;

can be either a multiplication if a is a variable, or a pointer declaration if a is a type. Each of these leads to a different parse tree, so the parser must know what a is.

This means that parsing and name resolution cannot be performed in separate passes, but must be done in one pass, leading to the requirement to pre-declare names.

Upvotes: 0

Related Questions