Advanced Namespace Techniques
Use unnamed namespaces for file-private definitions and inline namespaces for versioning.
What Are Unnamed and Inline Namespaces?
An unnamed namespace is a namespace written with no name at all, and an inline namespace is one written with the inline keyword in front of it. Both break the usual bargain a namespace makes with you. A normal namespace takes its members out of circulation and hands them back only when you write the qualification; these two hand their members straight to the enclosing namespace, so a caller in the same scope can use them with no prefix.
That much they share. What separates them is linkage, and the difference is total: one of them seals every name it contains inside a single file, and the other leaves linkage exactly as it found it. Because of that, the two features solve unrelated problems and almost never appear in the same piece of code for the same reason.
Two Effects, and Only One Is Shared
Every namespace form in the language can be placed on two axes: whether the parent scope needs a qualification to reach inside, and what linkage the members end up with.
| Form | Reachable from the parent without a prefix? | Linkage of its members | The problem it solves |
|---|---|---|---|
namespace Metrics { } |
no, callers write Metrics:: |
left alone | grouping related names and keeping them from colliding |
namespace { } |
yes | forced to internal | keeping a file's working parts out of every other file |
inline namespace rev2 { } |
yes | left alone | deciding which revision an unqualified name refers to |
The middle column is the family resemblance and the reason these two get taught in one lesson. The right-hand column is what you actually use them for, and there the resemblance ends.
Everything in the rest of this lesson is a consequence of that table.
Sealing a File's Private Section
Leave the name off a namespace and every declaration inside it gets internal linkage: usable throughout the file that wrote it, invisible to every other translation unit.
#include <iostream>
namespace // no name, so every declaration below is sealed into this file
{
constexpr int g_bytesPerKibibyte{1024};
int toKibibytes(int bytes)
{
return bytes / g_bytesPerKibibyte;
}
}
int main()
{
std::cout << "snapshot: " << toKibibytes(2097152) << " KiB" << '\n';
return 0;
}
This prints:
snapshot: 2048 KiB
Look at the call in main(). There is no prefix, and there could not be one, because the namespace has no name to write. Its members behave as though they had been declared directly in the enclosing scope, which here is the global namespace.
So the block did not hide anything from this file. What it changed is what the linker is told, and for functions the effect matches putting static on each declaration one at a time:
#include <iostream>
constexpr int g_bytesPerKibibyte{1024}; // constexpr globals are internal already
static int toKibibytes(int bytes) // static: this name never reaches the linker
{
return bytes / g_bytesPerKibibyte;
}
int main()
{
std::cout << "snapshot: " << toKibibytes(2097152) << " KiB" << '\n';
return 0;
}
This prints the same thing:
snapshot: 2048 KiB
Two declarations is a fair fight between the two spellings. Twenty is not. The unnamed namespace states the intent once and covers everything you put inside it, including declarations static refuses to accept at all, such as the type definitions the next chapter introduces. It also marks a boundary in the file: everything between those braces is implementation detail, and a reader can see where that section starts and stops without checking each line for a keyword.
When a source file has a private section made of several helpers, wrap the whole section in one unnamed namespace instead of repeating
static on every declaration. Reserve it for .cpp files, since a header is the one place the feature works against you.
The Same Name in Two Files, No Argument
Internal linkage is easy to state and hard to believe until two files have been caught disagreeing about a name. Here two translation units each define a helper called scale, and each one means something different by it.
This example is built from three files, so the in-browser runner, which compiles a single file, cannot run it. Save all three in one directory and compile them together, for example
g++ -std=c++20 main.cpp archive.cpp -o sizes.
archive.h:
#pragma once
void reportArchiveSize(int bytes);
archive.cpp:
#include "archive.h"
#include <iostream>
namespace
{
int scale(int bytes) // archive.cpp's own scale, invisible to every other file
{
return bytes / 1024;
}
}
void reportArchiveSize(int bytes)
{
std::cout << "archive.cpp reports " << scale(bytes) << " KiB" << '\n';
}
main.cpp:
#include "archive.h"
#include <iostream>
namespace
{
int scale(int bytes) // an unrelated scale that happens to share the name
{
return bytes / 1000;
}
}
int main()
{
reportArchiveSize(2097152);
std::cout << "main.cpp reports " << scale(2097152) << " kB" << '\n';
return 0;
}
Compiled together, this prints:
archive.cpp reports 2048 KiB
main.cpp reports 2097 kB
One program, two functions with identical names and identical signatures, no complaint from anybody. reportArchiveSize picks up the divisor from its own file and main picks up the divisor from its own file, and neither has any way of naming the other's. The header is the only channel between them, and it carries exactly one declaration.
Take both namespace blocks away and the program stops linking. The two definitions of scale would then have external linkage, the linker would be handed the same symbol twice, and it would report a multiple definition rather than pick a winner. The unnamed namespaces are what turn a name clash into a non-event.
Why a Header Is the Wrong Home for One
The mechanism that makes the example above work is the same mechanism that makes an unnamed namespace in a header a bad idea.
A header is not a file the compiler sees; it is text pasted into every source file that includes it. Paste an unnamed namespace into ten translation units and you have not shared one thing ten times, you have created ten separate things that happen to look alike. Ten variables, each with its own value, where the author almost certainly intended one. Writing to it in one file leaves the other nine untouched, and the bug that follows is difficult to see because the source reads as though a single object exists.
Ten copies of the contents also land in the final binary, since nothing is shared and nothing can be folded together. Worse, if the header also defines a function that uses those members, that function has external linkage while its body refers to a different entity in every file, which breaks the one-definition rule outright and is the kind of error a compiler is not required to report. SEI CERT rule DCL59-CPP is written up around exactly this failure.
Keep unnamed namespaces out of header files. Every file that includes the header receives its own private copy of the contents, which quietly multiplies variables that were meant to be shared and can put the program in violation of the one-definition rule.
Changing a Function Without Renaming It
Now the other variant, which has nothing to do with linkage.
Picture a small library whose header offers Metrics::average, already called from a few hundred places across a codebase you do not own. You have decided the arithmetic is wrong: it truncates when it should round. Change the body and every existing caller silently gets different numbers. Add average2 beside it and the name is wrong forever, plus the next fix gives you average3.
An inline namespace turns that into a decision the library makes rather than a decision every call site makes. Put each revision in its own namespace, mark exactly one of them inline, and the members of that one are reachable from the parent namespace with no extra qualification. The unmarked revisions stay where they are, fully available, but only to a caller who names them.
#include <iostream>
namespace Metrics
{
namespace rev1 // the original: integer division, so it truncates
{
int average(int total, int samples)
{
return total / samples;
}
}
inline namespace rev2 // the current default: rounds to nearest
{
int average(int total, int samples)
{
return (total + samples / 2) / samples;
}
}
}
int main()
{
std::cout << "asked for rev1: " << Metrics::rev1::average(18, 4) << '\n';
std::cout << "asked for rev2: " << Metrics::rev2::average(18, 4) << '\n';
std::cout << "asked for neither: " << Metrics::average(18, 4) << '\n';
return 0;
}
This prints:
asked for rev1: 4
asked for rev2: 5
asked for neither: 5
The third line is the whole feature. Metrics::average was never declared directly in Metrics; it lives one level down in rev2, and the inline keyword is what lifts it into the parent so an unqualified lookup finds it. Two functions with the same signature sit in the same enclosing namespace and there is no ambiguity, because only one of the two revisions has been promoted.
Note also that nothing here mentions linkage. rev1::average and rev2::average both have external linkage, exactly as they would in ordinary namespaces, and both are callable from any file that includes the header. The inline keyword decides what a bare name means; it does not decide who can see the name.
The
inline in inline namespace is unrelated to the inline from the inline functions and variables lesson. That one relaxes the one-definition rule so a definition may appear in every translation unit. This one selects which nested namespace an unqualified name resolves to. Same spelling, separate features.
Moving the Default Between Revisions
The interesting property of the arrangement is that the default is one keyword in one file. Move the keyword and every unqualified call site follows, without any of them being edited.
Suppose the rounding change is too disruptive to enable straight away, so the library ships it but leaves the old behaviour in charge until callers have had a chance to migrate. Only the inline moves:
#include <iostream>
namespace Metrics
{
inline namespace rev1 // still the default while callers migrate
{
int average(int total, int samples)
{
return total / samples;
}
}
namespace rev2 // available, but only to callers who ask for it by name
{
int average(int total, int samples)
{
return (total + samples / 2) / samples;
}
}
}
int main()
{
std::cout << "asked for rev1: " << Metrics::rev1::average(18, 4) << '\n';
std::cout << "asked for rev2: " << Metrics::rev2::average(18, 4) << '\n';
std::cout << "asked for neither: " << Metrics::average(18, 4) << '\n';
return 0;
}
The first two lines are unchanged, because a fully qualified call always gets the revision it names. The third has flipped:
asked for rev1: 4
asked for rev2: 5
asked for neither: 4
That gives a library two moves. Leaving inline on the old revision keeps every existing program compiling and behaving as before, and lets curious callers opt in by writing Metrics::rev2::. Moving inline to the new revision makes the improvement the default for everyone, and leaves a way out for the caller who cannot take it yet, at the cost of that caller having to write Metrics::rev1:: at each site.
Reach for an inline namespace when you need two revisions of the same interface alive at once and want the choice of default to be reversible. If you are only ever going to ship one revision, an ordinary namespace says so more clearly.
Putting One Inside the Other
The two variants can be combined, and there is a right way and a nearly-right way to do it.
The nearly-right way is to write inline namespace with no name. That is legal, and it does both jobs at once: the members get internal linkage and they are reachable from the parent with no prefix. What it throws away is the ability to refer to the revision. There is no name to write, so no caller can pin itself to that revision explicitly, no namespace alias can point at it, and no using-declaration can single it out. That is the entire purpose of an inline namespace, discarded to save a word.
Nesting an unnamed namespace inside a named inline one costs one extra line and keeps all of it:
#include <iostream>
namespace Metrics
{
inline namespace rev2
{
namespace // internal linkage, still reachable as Metrics::rev2::roundedAverage
{
int roundedAverage(int total, int samples)
{
return (total + samples / 2) / samples;
}
}
}
}
int main()
{
std::cout << "through the parent namespace: " << Metrics::roundedAverage(18, 4) << '\n';
std::cout << "through the revision name: " << Metrics::rev2::roundedAverage(18, 4) << '\n';
return 0;
}
This prints:
through the parent namespace: 5
through the revision name: 5
Both spellings reach the same function. Metrics::roundedAverage works because rev2 is inline, and Metrics::rev2::roundedAverage works because rev2 has a name to write. The inner unnamed namespace supplies the internal linkage without taking either route away.
Which One Do You Reach For?
| What you are trying to do | Use | Why |
|---|---|---|
| stop other files from calling a helper | unnamed namespace in the .cpp file |
its members get internal linkage |
| do the same for a single declaration | static, or an unnamed namespace |
identical effect for functions and variables; only the namespace covers types |
| ship a changed function without breaking callers | inline namespace per revision | the unqualified name resolves to whichever revision is marked inline |
| let callers pin themselves to one revision | inline namespace per revision | the unmarked revisions stay reachable by their own names |
| group related names to avoid collisions | ordinary named namespace | neither variant is involved; qualification is the point |
| share a constant across files from a header | neither | an unnamed namespace in a header gives each file its own copy |
Looking Forward
The next lesson closes this chapter with a review of scope, duration, and linkage together, which is the framework the unnamed namespace fits into. Program-defined types arrive in the following chapter, and they are the case that makes the unnamed namespace more than a convenience, since static cannot be applied to a type definition at all. Inline namespaces turn up again if you ever read the source of a standard library implementation, where they are used to keep several ABI-incompatible versions of the same class in one header.
Key Terminology
Unnamed namespace: a namespace written without a name. Its members are reachable from the enclosing namespace without qualification and are given internal linkage.
Anonymous namespace: another name for the same thing, used interchangeably in most codebases.
Inline namespace: a namespace declared with the inline keyword. Its members are reachable from the enclosing namespace without qualification, and their linkage is untouched.
Internal linkage: the property that lets a name be used anywhere in its own translation unit while keeping it unreachable from every other one.
Translation unit: one source file after the preprocessor has finished with it, headers and all, which is the unit the compiler actually compiles.
Summary
- Both variants let the enclosing namespace reach their members with no qualification; that is the only behaviour they have in common
- An unnamed namespace additionally gives every declaration inside it internal linkage, so the contents cannot be reached from another translation unit
- For a function or variable, an unnamed namespace does the same job as
staticon each declaration, but it states the intent once, marks the file's private section clearly, and also covers type definitions, whichstaticcannot - Two translation units may each define an identically named entity inside an unnamed namespace, and they remain separate entities rather than a link-time collision
- An unnamed namespace does not belong in a header, because every including file receives its own private copy of the contents, which multiplies objects meant to be shared and can violate the one-definition rule
- An inline namespace leaves linkage alone; its job is to decide which nested revision an unqualified name resolves to
- Marking one revision
inlinemakes it the default for unqualified callers while the other revisions stay available under their own names - Moving the
inlinekeyword changes the default for every unqualified call site without editing any of them - A namespace may be both inline and unnamed, but nesting an unnamed namespace inside a named inline one is preferable, since it keeps a name you can qualify with
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Advanced Namespace Techniques - Quiz
Test your understanding of the lesson.
Practice Exercises
Unnamed and Inline Namespaces
Explore unnamed namespaces for internal linkage and inline namespaces for versioning.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!