Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of my career fighting with header files. You know the drill: you create a .h, you add the #ifndef guards, you declare the functions, then you jump over to the .cpp to actually write the logic. It's a ritual we've just accepted, but it's fundamentally inefficient. The compiler has to parse that header over and over again every time it's included in a different file. It's like reading the instruction manual from page one every single time you want to use a screwdriver.

The Header Headache

Let's start with something typical. I want to build a small physics helper for a game—something to handle basic 2D vector math. In the old world, I'd have a Vector2D.h and a Vector2D.cpp. If I forgot the include guards, I'd get those nightmare "redefinition" errors. If I changed one private member in the header, every single file that included it would need to be recompiled. It's a slog.

C++20 modules are supposed to fix this by treating a piece of code as a single unit that the compiler processes once. Let's see if we can actually make it happen.

Testing the Export Keyword

I'm going to create a file called Physics.cppm (the .cppm extension is common for module interfaces, though it varies by compiler). I'll start by declaring the module:

export module Physics;

export float calculateDistance(float x1, float y1, float x2, float y2) {
    return __builtin_sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}

Notice that export keyword. I'm telling the compiler, "Hey, let the outside world see this function." Now, let's try to use it in main.cpp using import instead of #include.

import Physics;
#include <iostream>

int main() {
    std::cout << "Distance: " << calculateDistance(0, 0, 3, 4) << std::endl;
    return 0;
}

This works. It's clean. No guards, no splitting declarations from definitions. But here is where it gets interesting: what happens if I have logic that the module needs, but I don't want the rest of the program to touch?

Hidden Logic and Module Boundaries

I realize my distance formula is a bit messy. I want a helper function to handle the squaring of numbers, but that's an internal detail. I don't want some other developer using square() throughout the codebase just because it exists in my physics module. Let's adjust the module:

export module Physics;

// No 'export' here!
float square(float n) {
    return n * n;
}

export float calculateDistance(float x1, float y1, float x2, float y2) {
    return __builtin_sqrt(square(x2 - x1) + square(y2 - y1));
}

Now, if I go back to main.cpp and try to call square(5.0f), the compiler throws a fit. It tells me square is not a member of the module Physics. This is a huge win. In the header file era, if you wanted something "private," you had to hide it in a separate .cpp file or put it in a detail namespace and pray that no one ignored the naming convention. With modules, the boundary is enforced by the language itself.

The Mixing Problem

I tried to import <iostream> instead of #include <iostream>, and depending on the compiler version, it either worked perfectly or crashed my build. This is the current "growing pain" of modules. While C++20 allows header units (importing traditional headers), the tooling is still catching up.

My rule of thumb right now: use import for your own modules and #include for the old-school standard library headers until your specific compiler toolchain tells you otherwise. It's a weird hybrid state, but it's the reality of transitioning a 40-year-old language to a new paradigm.




📋 Practical Task

Exercise: Building a Coordinate Transformation Module

Your task is to create a module that handles converting coordinates between a "World Space" and a "Screen Space."

  • Create a module named CoordinateSystem.
  • Inside the module, create a private helper function called applyOffset that takes a value and an offset and returns the sum. This function should not be exported.
  • Create and export a function worldToScreen that takes worldX, worldY and offsetX, offsetY. It should use applyOffset internally to calculate the final screen coordinates and return them (you can use a simple struct Point { float x, y; }; for this).
  • In your main.cpp, import the CoordinateSystem module and use worldToScreen to transform a point.
  • Try to call applyOffset from main.cpp and verify that the compiler prevents you from doing so.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.