C++ Include Errors: Fixing “No such file or directory”
Key takeaways
Resolve #include failures: typos, -I paths, case sensitivity, circular dependencies, forward declarations, and #pragma once vs include guards.
Header discipline: header files.
Introduction: “The compiler can’t find my header”
Include errors are among the most common early C++ issues. Causes include wrong paths, circular includes, and missing forward declarations.
// Typo
#include <iostrem>
// fatal error: iostrem: No such file or directory
It helps to know who produces this error. #include is handled by the preprocessor, before the compiler sees any C++: it searches a list of directories for a file with exactly that name and pastes its contents in place. “No such file or directory” is a fatal error because nothing after that point can be compiled meaningfully, so it is always the first error to fix; the dozens of “was not declared” errors that sometimes follow it are consequences. The other group of include problems in this article is different in kind: the file is found, but the declarations it provides are incomplete or arrive in the wrong order, which the compiler reports as incomplete type or does not name a type.
This article covers:
- Five major causes of include failures
- How to configure include paths
- Breaking circular includes
- Forward declarations
- Include guards vs
#pragma once
Five common causes
Filename typo
#include <iostrem>
Fix: Spell standard headers exactly.
#include <iostream>
File does not exist
#include "myheader.h"
Verify the file exists: ls myheader.h or find . -name "myheader.h".
A quoted include is searched first relative to the directory of the file that contains the #include, not the directory you run the compiler from and not the project root. So #include "myheader.h" in src/net/client.cpp looks in src/net/ first. A file that exists elsewhere in the project is only found if that directory is on the include path. Generated headers (from protobuf, Qt’s moc, or a configure_file step) are a common variant: the file does not exist until a build step runs, and building a single target or a clean checkout without that step fails with this error.
Include path not set
#include "utils/helper.h"
Add search paths:
g++ -I./src -I./include main.cpp
target_include_directories(myapp PRIVATE
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/include
)
Each -I directory is a root that include paths are resolved against, so -I./src plus #include "utils/helper.h" finds ./src/utils/helper.h. Adding the deepest directory (-I./src/utils and #include "helper.h") also works but invites name collisions between same-named headers in different folders. In CMake, the PRIVATE/PUBLIC/INTERFACE keyword matters: PRIVATE directories are used only when compiling myapp itself, while PUBLIC ones are also passed to every target that links against it. A library whose public headers include each other needs PUBLIC (or INTERFACE) include directories; otherwise the library compiles and its users get “No such file or directory”. Prefer ${CMAKE_CURRENT_SOURCE_DIR} over ${CMAKE_SOURCE_DIR} so the project still works when it is added as a subdirectory of another project.
Case mismatch (mainly Linux)
#include "MyHeader.h" // actual file: myheader.h
Fix: Match filename case exactly.
Windows (NTFS) and macOS (APFS in its default configuration) are case-insensitive, so this include works on a developer laptop and fails on a Linux build server or in a Docker image. That asymmetry is why the error so often appears first in CI. Renaming a file only by case in Git on those systems is also tricky (git mv myheader.h MyHeader.h is the reliable way), so a case mismatch can hide in the repository for a long time. Clang has a -Wnonportable-include-path warning that reports exactly this on case-insensitive systems.
Circular includes
Mutual includes between headers that need complete types can produce incomplete type errors. Fix: Forward declarations (next sections).
Include path configuration
<> vs ""
#include <iostream> // system headers
#include "myheader.h" // project headers
The difference is only in where the search starts. For "...", the compiler first looks in the including file’s directory (and in -iquote directories with GCC and Clang), then falls back to the same list used for <...>: the -I directories, then the system directories. For <...>, the current directory is skipped. So a project header included with angle brackets still works if its directory is on -I, which is how most libraries are consumed (#include <boost/asio.hpp>). The convention of <> for external and "" for project headers is about readability and avoiding accidental shadowing: a local file named string.h next to your source can hide the system one if included with quotes.
GCC/Clang -I
g++ -I./src -I./include src/main.cpp
Visual Studio
Project Properties → C/C++ → General → Additional Include Directories
Example: $(ProjectDir)src;$(ProjectDir)include
MSVC reports the same problem as fatal error C1083: Cannot open include file: 'myheader.h': No such file or directory. A frequent Visual Studio trap is setting the directory for only one configuration or platform: the property page has Configuration and Platform drop-downs at the top, and a path added for Debug|x64 is missing in Release|x64. Choose “All Configurations” before editing. When the path looks right but the error persists, print what the compiler actually sees: /showIncludes for MSVC, or echo | g++ -E -x c++ - -v for GCC, which lists the #include <...> search starts here: directories in order.
Circular includes
Problem sketch
Player.h includes Weapon.h and holds a Weapon by value; Weapon.h includes Player.h. The preprocessor may never see a complete type.
Here is what happens step by step when main.cpp includes Player.h first. The guard PLAYER_H is defined, then Player.h includes Weapon.h. Weapon.h includes Player.h again, but the guard is already defined, so that include expands to nothing. Weapon.h continues and uses Player, which has not been declared yet, because the rest of Player.h has not been processed. The compiler reports error: 'Player' does not name a type inside Weapon.h, a file that looks perfectly correct on its own. Include guards prevent infinite recursion, but they cannot make two headers that each need the other’s full definition work. Which header fails depends on which one was included first, which is why the error moves around as you reorder includes.
Fix: forward declaration + pointer
// Player.h
#ifndef PLAYER_H
#define PLAYER_H
class Weapon;
class Player {
Weapon* weapon_;
public:
void setWeapon(Weapon* w);
};
#endif
// Player.cpp
#include "Player.h"
#include "Weapon.h"
void Player::setWeapon(Weapon* w) {
weapon_ = w;
}
class Weapon; tells the compiler that a class with this name exists, which is enough to declare pointers and references to it, because every pointer has the same size regardless of what it points to. Player.h no longer needs Weapon.h at all, the cycle is gone, and Player.cpp, which calls members of Weapon, includes the full definition. A side benefit is build speed: files that include Player.h no longer recompile when Weapon.h changes.
Prefer smart pointers
// Player.h
#include <memory>
class Weapon;
class Player {
std::unique_ptr<Weapon> weapon_;
public:
Player();
~Player(); // declared here, defined in Player.cpp
};
// Player.cpp: #include "Weapon.h", then
// Player::Player() = default; Player::~Player() = default;
std::unique_ptr works with a forward-declared type, with one catch that trips up almost everyone the first time. The pointer’s deleter needs the complete type at the point where the destructor is instantiated. If Player has no user-declared destructor, the compiler generates one inline in every file that destroys a Player, where Weapon is incomplete, and the build fails with an error like invalid application of 'sizeof' to incomplete type 'Weapon' or can't delete an incomplete type, pointing deep into <memory>. Declaring the destructor (and the constructor, and any move operations) in the header and defining them, even as = default, in Player.cpp after #include "Weapon.h" fixes it. std::shared_ptr does not have this restriction, because its deleter is captured when the pointer is created.
Forward declarations
When they work
class MyClass;
MyClass* ptr;
MyClass& ref = *ptr;
void foo(MyClass* obj);
When they do not
class MyClass;
MyClass obj; // needs size
ptr->method(); // needs complete type in general
sizeof(MyClass) // needs complete type
class Derived : public MyClass {}; // needs base definition
Pattern
- Header: forward declare + pointer/
unique_ptras needed - Source:
#includefull definitions
The rule behind the lists: anything that needs the size or layout of the class (a by-value member, a local object, sizeof, a base class) or its members (calling a method, accessing a field, even an inline function in the header that does so) requires the complete definition. Declaring a function that takes or returns MyClass by value is fine with only a forward declaration; the complete type is needed where the function is defined or called. Standard library types are the exception: forward-declaring std::string or other std names yourself is undefined behavior (use <iosfwd> for stream forward declarations instead).
Header guards
#ifndef style
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass { };
#endif
#pragma once (common choice)
#pragma once
class MyClass { };
| Topic | #ifndef | #pragma once |
|---|---|---|
| Standard | ISO macro guards | Not ISO, but ubiquitous |
| Typing | Verbose | Short |
| Name clashes | Possible | Avoided |
| Speed | OK | Often faster on some compilers |
Both make a header safe to include twice in one translation unit, which happens constantly through indirect includes; without either, the second inclusion produces error: redefinition of 'class MyClass'. Each has one characteristic failure. With #ifndef guards, copying a header to create a new one and forgetting to rename the macro gives both files the same guard, and whichever is included second silently becomes empty; the resulting “not declared” errors are baffling until you look at the guard. With #pragma once, the compiler has to decide whether two paths name the same file, and that can go wrong when the same header is reachable through a symlink, a hard link or two copies in different directories (a vendored copy and an installed copy, for example). Modern GCC and Clang handle guards as efficiently as #pragma once, so the speed row rarely matters today. Most projects simply pick one and use it everywhere.
Real-world examples
External library
#include <boost/asio.hpp>
Install dev packages and/or add -I / CMAKE_PREFIX_PATH / toolchain from vcpkg.
For third-party libraries, the header missing is usually a packaging question rather than a code one. On Debian and Ubuntu, the runtime package (libboost-system1.83.0) does not contain headers; the -dev package (libboost-dev) does. With CMake, avoid hard-coding -I/usr/include/... and let find_package(Boost REQUIRED) plus target_link_libraries(myapp PRIVATE Boost::headers) add the right include directories, which then also work on machines where the library lives elsewhere. With vcpkg or Conan, the headers only appear if CMake is configured with the package manager’s toolchain file; forgetting -DCMAKE_TOOLCHAIN_FILE=... is the usual cause of “installed but not found”.
Project layout
project/
src/main.cpp
include/utils.h
g++ -I./include src/main.cpp
Summary
Checklist
- No typos in
#includenames? - File exists on disk?
-
-I/target_include_directoriesconfigured? - Case matches on case-sensitive FS?
- Cycles broken with forward declarations?
- Guards or
#pragma onceon every header?
Rules
- Standard library: #include <…>
- Project headers: #include ”…”
- Use #pragma once (or guards) consistently.
- Resolve cycles with forward declarations and includes in
.cppfiles. - Keep include roots explicit in build settings.
Related posts (internal)
Tracking down include problems
When an include error does not make sense, I look at what the preprocessor actually did rather than at the source. g++ -H file.cpp prints every header as it is included, indented by depth, which shows immediately which file pulled in which and where a cycle closes. g++ -E file.cpp writes the fully preprocessed output, so you can search it for the class the compiler claims is undeclared and see whether its definition is present and where. For “No such file” errors, the exact filename in the diagnostic, including case and any directory part, compared against the search path printed by echo | g++ -E -x c++ - -v, resolves nearly every case.
Two habits prevent most of these errors. Make every header self-contained: it should compile when included first in an empty .cpp file, which you can enforce by including each header first in its own .cpp. And keep headers minimal: forward-declare instead of including where possible, and move includes into .cpp files. Tools like include-what-you-use automate the second habit, and it also reduces rebuild times, because every unnecessary include in a widely used header is recompiled by every file that includes it.
Closing
Most include errors are paths or cycles. Configure include directories, use #pragma once, and forward-declare to break mutual dependencies. Next: Read the compilation process for preprocessing and linking context.
More related posts
Frequently Asked Questions (FAQ)
Q. It builds on Windows but fails with “No such file or directory” on Linux. Why?
A. Windows file systems are case-insensitive by default, so #include "MyHeader.h" finds myheader.h there, while Linux file systems treat them as different files. Backslashes in include paths such as #include "utils\math.h" are another Windows-only habit that is not portable. Match the exact filename case and always use forward slashes in #include directives.
Related Articles
- C++ Header Files: Declarations and Include Guards
- C++ Forward Declaration: Reduce Includes, Break Cycles
- C++ Compilation Pipeline — Preprocessing and Compilation