nyronium
nyronium

Reputation: 1278

Fixing self-blocking includes in a module based library

I have written a simple templated, module-based header library. With module-based, I mean that one can include only string.h or dynarray.h and the header will pull in all of its dependencies.

Now I'm facing an issue with missing types because of the way this system works. A module does:

Unfortunately, in some situations, two interfaces need to be available before including any implementations. I have broken down the problem here:

string.h

#pragma once

// A string depends on a DynArray.
#include "dynarray.h"

template<typename E>
class String {
public:
    DynArray<E> arr;
    /* ... */
};

// Include the implementation of all the different functions (irrelevant here)
#include "string_impl.h"

dynarray.h

#pragma once

// The dynarray header has no direct dependencies

template<typename E>
class DynArray {
public:
    /* ... */
    E& Get(int index);
};

// Include the implementation of all the different functions
#include "dynarray_impl.h"

dynarray_impl.h

#pragma once

// The dynarray implementation needs the OutOfRangeException class
#include "out_of_range_exception.h"

template<typename E>
E& DynArray<E>::Get(int index) {
    if (index >= size) {
        throw OutOfRangeException("Some error message");
    }
}

out_of_range_exception.h

class OutOfRangeException {
public:
    String message;
    OutOfRangeException(String message) {
        /* ... */
    }
};

Due to including the implementation of a module at its bottom, when including string.h somewhere, the content of dynarray_impl.h and with it out_of_range_exception.h comes before the string class interface. So String is not defined in OutOfRangeException.

Obviously, the solution is to delay only the implementation part of dynarray (dynarr_impl.h) after the definition of the string interface. The problem is that I have no idea how to do this without creating some kind of common header file, which is not compatible with a module based approach.

Upvotes: 2

Views: 40

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275760

Your problem is that you have one file for both interface and implementation.

#includeing that file represents both depending on the interface of X and the implementation of X.

Sometimes you just want to depend on the interface of X.

X interface:

  • #include all dependencies of the interface
  • Define an interface class X.

X implementation:

  • #include the interface
  • #include all dependencies of the implementation
  • Define the implementation of class X.

In a sense, these are two separate modules, where one depends on the other. This permits clients to depend only on the interface of another type, or only on its implementation, or first on the interface, then later on the implementation.

Usually you can just #include "X.h", except when you have a circular dependency of implementations. Then somewhere you have to break the chain with a #include "X_interface.h"


If you really want to use a single header file, you can do away with #pragma once and have header files that can be included in "two modes". This can massively slow down build times as any such mechanism will require the compiler to open files just to check if there is any code there; most compilers can detect #ifdef header-guards and #pragma once and avoid reopening files it knows won't contain anything of interest. But a fancy "can be included multiple times in different modes" header file cannot be handled by that technique.

Upvotes: 2

Related Questions