Hello StackOverflow!
I'm trying to make some kind of algebraic solving code. I've laid out some basic structure. But I stumble uppon the error that a subclass finds the base-class being undefined. I've tried removing #pragma once
and removing and changing include statements here and there. But I cannot figure it out.
I think it has something to do with the files including eachother, but from what I thought I knew from before. That is supposed to be possible thanks to #pragma once
and #ifndef
. Please advice me on what I am doing wrong here!
Expression.h:
#pragma once
#ifndef MATH_EXPRESSION_H
#define MATH_EXPRESSION_H
#include <string>
#include "Variable.h"
#include "Value.h"
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0;
virtual operator std::string() const = 0;
template<class T>
bool isType() {
return dynamic_cast<T*>(this);
}
};
#endif
Variable.h:
#pragma once
#ifndef MATH_VARIABLE_H
#define MATH_VARIABLE_H
#include "Expression.h"
class Variable : public Expression {
};
#endif
Value.h:
#pragma once
#ifndef MATH_VALUE_H
#define MATH_VALUE_H
#include "Expression.h"
class Value : public Expression {
public:
float value;
Value(float value) : value(value) {}
public:
Expression* eval() { return this; }
Expression* eval(const Variable* v, Value* val) {
return this->eval();
}
operator std::string() const {
return std::to_string(value);
}
};
#endif
And this is the build error I am trying to solve:
Variable.h(7,36): error C2504: 'Expression': base class undefined
You have circular references between your header files.
If Expression.h
is included in a translation unit before either Variable.h
and Value.h
are included, Expression.h
will define the MATH_EXPRESSION_H
guard and then include Variable.h
before declaring the Expression
class. Variable.h
will include Expression.h
again, which is effectively a no-op because of the MATH_EXPRESSION_H
guard. So, the Variable
class will be referring to an Expression
type that hasn't been declared yet, hence a compiler error.
If Variable.h
is included in a translation unit before Expression.h
is included, Variable.h
will define the MATH_VARIABLE_H
guard and then include Expression.h
before declaring the Variable
class. Expression.h
will include Variable.h
again, which is effectively a no-op because of the MATH_VARIABLE_H
guard. Expression.h
will then include Value.h
, which will include Expression.h
again, which is a no-op (see above). So, the Value
class will be referring to an Expression
type that hasn't been declared yet, hence a compiler error.
If Value.h
is included in a translation unit before Expression.h
is included, Value.h
will define the MATH_VALUE_H
guard and then include Expression.h
before declaring the Value
class. Expression.h
will include Variable.h
, which will include Expression.h
again, which is a no-op (see above). Then Expression.h
will include Value.h
, which is a no-op due to the MATH_VALUE_H
guard. So, the Variable
class will be referring to an Expression
type that hasn't been declared yet, hence a compiler error.
Let's look at this with actual code.
If Expression.h
is included first, this is what the compiler will see after the preprocessor directives have been processed:
// content from <string> ...
class Variable : public Expression { // <-- error, Expression not defined!
};
class Value : public Expression { // <-- error, Expression not defined!
...
public:
Expression* eval() { ... } // <-- error, Expression not defined!
Expression* eval(const Variable* v, Value* val) { ... } // <-- error, Expression not defined!
...
};
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
};
If Variable.h
is included first:
// content from <string> ...
class Value : public Expression { // <-- error, Expression not defined!
...
public:
Expression* eval() { ... } // <-- error, Expression not defined!
Expression* eval(const Variable* v, Value* val) { ... } // <-- error, Expression and Variable not defined!
...
};
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- error, Variable not defined!
...
};
class Variable : public Expression { // <-- OK!
};
If Value.h
is included first:
// content from <string> ...
class Variable : public Expression { // <-- error, Expression not defined!
};
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- error, Value not defined!
...
};
class Value : public Expression { // <-- OK!
public:
Expression* eval() { ... } // <-- OK!
Expression* eval(const Variable* v, Value* val) { ... } // <-- OK!
...
};
So, this is a no-win situation regardless of which header is included first.
Since you are using only pointers to the various classes in function parameter types and return types, and not accessing any members of the classes, you can break this circular issue by replacing the #include
statements in Expression.h
with forward declarations instead, eg:
#pragma once
#ifndef MATH_EXPRESSION_H
#define MATH_EXPRESSION_H
#include <string>
// forward declarations
class Variable;
class Value;
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0;
virtual operator std::string() const = 0;
template<class T>
bool isType() {
return dynamic_cast<T*>(this);
}
};
#endif
The declaration of Expression
only needs to know that Variable
and Value
exist, not what they look like inside. Same with the declaration of Value
only needing to know that Variable
exists, not what it looks like inside.
So, let's look at what this change does to the code that the compiler sees after the preprocessor directives are processed.
If Expression.h
is included first:
// content from <string> ...
// forward declarations
class Variable;
class Value;
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
...
};
If Variable.h
is included first:
// content from <string> ...
// forward declarations
class Variable;
class Value;
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
};
class Variable : public Expression { // OK!
};
If Value.h
is included first:
// content from <string> ...
// forward declarations
class Variable;
class Value;
class Expression {
public:
virtual Expression* eval(const Variable* var, Value* val) = 0; // <-- OK!
...
};
class Value : public Expression { // <-- OK!
...
public:
Expression* eval() { ... } // <-- OK!
Expression* eval(const Variable* v, Value* val) { ... } // <-- OK!
...
};
This does mean that you will have to #include
the Variable.h
and Value.h
headers in any other source/header files that need to actually access the members of Variable
and Value
.
Whenever you are dealing with declarations that only use pointers/references to a type and not any members of that type, you should prefer a forward declaration for that type, not a header file include. This will ease the burden of the compiler, and avoid any potential circular issues.