Language[lang]
using namespace std: what it does and what to write instead
- Section
- Language [lang]
- Standard
- C++17
- Compilers
- MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
- Published
- Length
- 10 min read
In this answer8 clauses
- 1What using namespace std does
- 2Why count, byte or size becomes ambiguous
- 3The silent case: it compiles and calls std::count
- 4Every new standard adds names to std
- 5What to write instead of using namespace std
- 6Namespaces in C++, in one table
- 7Why std::cout << x needs no using: argument-dependent lookup
- 8A checklist, and the tools that check it
Abstract
using namespace std; is a using-directive: it declares no names, but until its scope ends, unqualified lookup also finds std’s names, as if declared in the global namespace. A global count, size or byte of yours clashes with a std name from your headers, and a function of yours can silently lose to a better-matching std overload. Don’t write it at global scope in a header (Core Guidelines SF.7); SF.6 allows it in a source file, where std::, or using std::cout; per name, keeps other std names out.

using namespace std; is a using-directive: it declares no names, but until its scope ends, unqualified lookup also searches std as if its names were declared in the global namespace. A global variable of yours named count, size or byte becomes ambiguous with the std name your headers declare, and a function of yours can silently lose to a better-matching std overload. Don’t write it at global scope in a header. In a source file the Core Guidelines accept it; std::, or using std::cout; per name, keeps other std names away from yours.
This answer is written for C++17, which added std::size and std::byte to the library and nested namespace definitions to the language (Since When’s C++17 table has the first compiler releases with the last two). C++20 added using enum, and C++20 and C++23 added more names to std.
The samples were built as C++17; the clash table, the byte sample and the two silent cases also as C++14, C++20 and C++23, and each newer name in the standard before it and the one that added it. Four compilers on three standard libraries built them: MSVC 19.44 (/W4 /permissive-) and Clang 19.1 on Microsoft’s STL (Windows x64), GCC 16.2 with libstdc++ and Clang 23.1 with libc++ (Compiler Explorer, Linux x86-64), the other three at -Wall -Wextra.
What using namespace std does
One function three ways: qualified, with a using-declaration, with the directive.
void print_qualified(const std::vector<int>& readings) {
std::cout << "readings: " << readings.size() << '\n';
}
void print_declared(const std::vector<int>& readings) {
using std::cout; // declares the one name cout, here
cout << "readings: " << readings.size() << '\n';
}
void print_directive(const std::vector<int>& readings) {
using namespace std; // lookup also searches std, up to the }
cout << "readings: " << readings.size() << '\n';
}— end example]
All four printed readings: 5 three times. The two using lines differ in kind:
using std::cout;is a using-declaration. It declarescoutin that scope, so a variable namedcoutalready declared there is an error at that line.using namespace std;is a using-directive. “A using-directive does not introduce any names” ([namespace.udir]): during unqualified lookup, the names ofstdappear as if they were declared in the nearest enclosing namespace that contains both the directive andstd.stdis declared in the global namespace, so forstdthat is the global namespace, wherever the directive is written.
That is the meaning of using namespace std;: lookup changes, declarations do not. So a directive inside namespace tally puts nothing into it, and a count declared there does not clash with std::count:
namespace tally {
using namespace std; // std's names act as if declared at global scope
int count = 0; // tally::count is one scope closer, so it wins
void add(const vector<int>& readings, int limit) {
for (int r : readings)
if (r > limit) ++count; // tally::count, not ambiguous
}
} // namespace tally— end example]
All four compiled it and printed 3. Unqualified lookup goes scope by scope, inner to outer, and stops at the first scope where the name is found. With <algorithm> included and the directive in force:
Your count is declared |
What count finds, on all four |
|---|---|
| In the function, as a local | yours: the block is searched before the global scope |
In namespace tally, beside the directive |
yours: tally is searched before the global scope |
| At global scope | yours and std::count at the same level: ambiguous |
Why count, byte or size becomes ambiguous
A variable of yours at global scope and a name of std are found at the same level, and unless both are functions, the use is ambiguous. The tally program counts readings above 10 in a global count:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int count = 0; // readings above the limit
int main() {
vector<int> readings{12, 7, 15, 10, 21};
for (int r : readings)
if (r > 10) ++count; // error: yours, or std::count?
cout << count << '\n';
}— end example]

None of the four compiled it. Both Clangs said:
error: reference to 'count' is ambiguous
note: candidate found by name lookup is 'count'
note: candidate found by name lookup is 'std::count'— end example]
GCC 16.2 printed the same first line, then there are 3 candidates, counting the execution-policy overload of std::count. MSVC printed error C2872: 'count': ambiguous symbol.
With ::count at both uses, the name is yours, and all four printed 3. A leading :: does not mean “not std”, though: with no cout of yours, ::cout compiled on all four and wrote to std::cout, found through the directive.
With only <iostream> and <vector> included, a global of yours against std; under each library, the first of C++14, C++17, C++20 and C++23 in which it clashed (it clashed in every later one too):
| Your global | In std since |
Microsoft’s STL (MSVC, Clang 19.1) | libstdc++ (GCC 16.2) | libc++ (Clang 23.1) |
|---|---|---|---|---|
int count |
before C++17 | C++14 | C++23 | C++14 |
int distance, int min, bool swap, int move, int left |
before C++17 | C++14 | C++14 | C++14 |
int data[], bool empty, const int size |
C++17 | C++14 | C++17 | C++17 |
typedef unsigned char byte |
C++17 | C++17 | C++17 | C++17 |
int ssize |
C++20 | C++20 | C++20 | C++20 |
int format |
C++20 | C++23 | C++23 | C++23 |
bool print, bool println |
C++23 | C++23 | C++23 | C++23 |
The standard itself guarantees some rows on every library, from the standard that added the name: <iostream> must include <ios> (left), <vector> must declare swap, size, data, empty and ssize, and from C++23 <ostream> declares print and println. The rest, count, distance, min, move, format and byte, came in through a library’s own includes. On libstdc++, std::count arrived only in C++23 mode, through <iostream>, <ostream>, <format>, <bits/unicode.h>, <bits/stl_algo.h>. With <algorithm> included, as in the tally program, it clashed on all four.
import std; (C++23) makes every declaration of the library reachable, so a directive after it brings in all of std’s names, not only the ones your headers happened to declare ([std.modules]).
The silent case: it compiles and calls std::count
A function of yours is not ambiguous with a std function of the same name: both go into one overload set, and overload resolution picks the better match. Beside the directive, a count(const vector<int>&, int) and a distance(int, int) of yours were called in every standard built. The trap is a std template that takes your arguments as they are where yours needs a conversion, even one that only adds const:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std; // the one line that differs
// yours: how many readings are above the limit
auto count(const int* first, const int* last, int limit) {
return std::count_if(first, last,
[limit](int r) { return r > limit; });
}
int main() {
std::vector<int> readings{12, 7, 15, 10, 21};
int* first = readings.data();
std::cout << count(first, first + readings.size(), 10) << '\n';
}— end example]
Without the directive, all four printed 3, the readings above 10. With it, all four printed 1: std:: takes the int* as it is, and that beats even the int* to const int* conversion yours needs ([over.ics.rank]), so the library’s function ran and counted the readings equal to 10. Nothing warned at -Wall -Wextra or /W4, as C++14, C++17, C++20 or C++23. Built again as C++17 with Clang’s -Weverything, MSVC’s /Wall and GCC’s -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wuseless-cast, no compiler said which function was called.
The same happens to a to_string of yours that formats volts, called with an int reading:
std::string to_string(double volts) {
return std::to_string(volts) + " V";
}
// ...
std::cout << to_string(readings[0]) << '\n';— end example]
Without the directive it printed 12.000000 V; with it, 12, from std::to_string(int), the exact match. Same on all four, every standard, no diagnostic.
Two cases are narrower. abs of a double holding -2.5, with only <cstdlib> included, changed on libstdc++ alone (2 without the directive, 2.5 with it): without the directive, the call reaches what <cstdlib> declared in the global namespace, and whether a <c…> header declares its names there is unspecified ([headers]). A move(Batch&) of yours called with a class derived from Batch lost to std::move on all four, but every compiler warned.
The directive is not the only way in. With none at all, a count of yours taking vector<int>::, called with readings.begin() and readings.end(), lost to std::count on all four: argument-dependent lookup found it through the iterators’ type, which has std among its associated namespaces on all three libraries (the standard leaves that type to the implementation), and it takes them as they are. With cbegin() and cend(), yours ran.
Every new standard adds names to std
Code with the directive that compiled under one -std can fail under the next, or after an upgrade that changes a compiler’s default standard (Clang vs GCC lists the defaults). A byte typedef for the wire format:
#include <cstddef>
#include <iostream>
using namespace std;
typedef unsigned char byte; // one byte of the wire format
int main() {
byte header = 0x2A; // ambiguous from C++17: std::byte
cout << int{header} << '\n';
}— end example]
As C++14, all four compiled it and printed 42. As C++17, C++20 and C++23, none did; GCC 16.2 named the other candidate 'enum class std::. size, data and empty (C++17) break this way on libstdc++ and libc++ only: Microsoft’s STL declares them under /std:c++14 too.
Each name below, as a global int or bool of yours with the directive and the name’s own header, compiled in the standard before and failed in the standard that added it, on all four:
| Name | Header | Ambiguous from |
|---|---|---|
clamp |
<algorithm> |
C++17 |
span |
<span> |
C++20 |
format |
<format> |
C++20 |
midpoint |
<numeric> |
C++20 |
expected |
<expected> |
C++23 |
print |
<print> |
C++23 |
Since When has the first compiler releases with std::clamp, std::span and std::format, and std::expected and std::print.
What to write instead of using namespace std
| Where | Write | Why |
|---|---|---|
| A header, at global scope | std::cout |
SF.7: every includer gets the directive |
| A source file, at the top | std::cout, using std::cout; per name, or the directive |
SF.6 accepts the directive for std and calls a .cpp file “a form of local scope”: the clashes above are its cost |
| Inside a function | using namespace std; |
it ends at the closing brace; inside, the same clashes |
| User-defined literals | using namespace std:: |
SF.7’s own exception |
| A long nested namespace | namespace fs = std:: |
an alias, written once |
SF.7, “Don’t write using namespace at global scope in a header file”, gives the reason: the directive takes away an includer’s ability to disambiguate, and makes the meaning of headers depend on the order they are included in.
SF.6 warns that the directive “can lead to name clashes, so it should be used sparingly”, and judges a clash “not particularly likely to lead to a resolution that is not an error”. The two silent calls above are that resolution, measured. For a header the rule stands; for a source file the choice is yours. All five forms in one file:
namespace fs = std::filesystem; // a long name, made short
using std::cout; // one name per declaration
int total(const std::vector<int>& readings) {
using namespace std; // contained: it ends at the }
return accumulate(begin(readings), end(readings), 0);
}
void report(const std::vector<int>& readings) {
using namespace std::literals; // "..."s, 30s and the like
const auto label = "tally"s; // a std::string
const auto window = 30s; // std::chrono::seconds
const fs::path file = fs::path{"logs"} / (label + ".csv");
cout << label << ": " << total(readings) << " in "
<< window.count() << " s, saved to "
<< file.generic_string() << '\n';
}— end example]
All four printed tally: 65 in 30 s, saved to logs/tally.csv. A directive inside a function limits where it applies, not what it risks: by the same rule, the names of std still act there as if declared at global scope. With <algorithm> included and a global count of yours:
int count = 0; // readings above the limit
void tally(const std::vector<int>& readings) {
using namespace std; // only inside this function
for (int r : readings)
if (r > 10) ++count; // error: still yours, or std::count?
cout << count << '\n';
}— end example]
All four rejected it, as at file scope; a local count beside the directive hid std::count and printed 3. With the directive inside main, the silent count call printed 1 on all four.
Against a variable of yours, a using-declaration fails where you wrote it, not at a use further down:
int count = 0; // readings above the limit
using std::count; // error here, at the declaration— end example]
All four rejected the using line; Clang: target of using declaration conflicts with declaration already in scope. Against a function of yours it joins the overload set instead: the silent count call, with using std::count; in place of the directive, printed 1 on all four. A using-declaration cures the ambiguity with a variable, not the silent case.
Namespaces in C++, in one table
A namespace is a named scope for declarations, and std is one. C has no namespace and prefixes its names instead (C++ vs C). The forms, and a sample that compiled as C++17 on all four:
| Form | What it does | Since |
|---|---|---|
namespace tally { } |
declares a namespace; a second block with the name reopens it | before C++17 |
int tally:: |
defines outside the block a function declared in it, as a source file does for its header | before C++17 |
namespace tally:: |
nested namespaces in one line | C++17; as C++14 MSVC rejects it, both Clangs warn, GCC 16.2 accepts it |
namespace { } |
unnamed: a unique namespace plus an implicit using-directive, internal linkage | before C++17 |
inline namespace v2 { } |
its members act as members of the enclosing namespace | C++11 |
namespace tw = tally:: |
an alias | before C++17 |
using enum Unit; |
the enumerators, as if by using-declarations | C++20; as C++17 MSVC and GCC reject it, both Clangs warn |
namespace tally { // a namespace
int limit = 10;
}
namespace tally { // reopened: adds to the same namespace
int above(int reading) { return reading > limit ? 1 : 0; }
}
namespace tally::wire { // nested, in one line (C++17)
using byte = unsigned char;
}
namespace { // unnamed: this file only, like static
int frames = 0;
}
namespace tally {
inline namespace v2 { // inline: its names are tally's too
int version() { return 2; }
}
}
namespace tw = tally::wire; // an alias— end example]
Your own declarations do not go in std: adding them is undefined behavior unless the standard allows it ([namespace.std]).
Why std::cout << x needs no using: argument-dependent lookup
An unqualified function call also looks in the namespaces associated with its arguments’ types ([basic.lookup.argdep]). That is how std::cout << "text" finds std’s non-member operator<< with no directive (for an int or a double, operator<< is a member of std::ostream, found through std::cout itself), and how a function of yours is found through its argument:
tally::Reading r{12};
std::cout << describe(r) << '\n'; // found in tally, through r
std::cout << r << '\n'; // tally::operator<<, through r
operator<<(std::cout, "std's operator<<, through std::cout\n");— end example]
All four printed reading 12, 12 mV and the string. The same rule is why generic code writes using std::swap; before an unqualified swap(a, b): the type’s own swap is found through its namespace, and std::swap is the fallback. A qualified std::swap(a, b) never looks at yours; C++20’s std::ranges::swap makes the unqualified call for you:
namespace tally {
struct Frame {
int id;
};
void swap(Frame& a, Frame& b) {
std::cout << "tally::swap\n";
std::swap(a.id, b.id);
}
} // namespace tally
int main() {
tally::Frame a{1}, b{2};
std::swap(a, b); // std::swap: yours is never looked at
{
using std::swap;
swap(a, b); // tally::swap, std::swap the fallback
}
std::ranges::swap(a, b); // C++20: tally::swap, no using needed
std::cout << a.id << ' ' << b.id << '\n';
}— end example]
Built as C++20, all four printed:
tally::swap
tally::swap
2 1— end example]
A checklist, and the tools that check it
Once a standard header had declared std, none of the four compilers warned about the directive itself at -Wall -Wextra or /W4, in a source file or in a file included the way a header is. Clang’s -Wheader-hygiene, off at those flags, reported the header case on both Clangs: using namespace directive in global context in header. No GCC flag or MSVC warning for the directive was found.
Two checks of clang-tidy 19.1.5 (Visual Studio’s, on Microsoft’s STL) cover it. google-build-using-namespace reported every directive except one naming std::literals, which it skips, as do not use namespace using-directives; use using-declarations instead, and was the only tool run that flagged the silent count program, by flagging its directive. google-global-names-in-headers reported it only in a header, as using declarations in the global namespace in headers are prohibited, and in a header included by the checked file only with -header-filter=.*.
- No
using namespaceat global scope in a header, exceptstd::literals. - In a source file,
std::orusing std::name;per name; a directive, at the top or inside a function, risks the same clashes (SF.6 counts both as local scope). - A using-declaration for a name you also define as a function joins your overloads, silently.
- No global of yours named like a
stdname:count,size,data,byte,format,print. - Before raising
-std, build each file that has a directive under the new standard. - In generic code,
using std::orswap; swap(a, b); std::ranges::swap(C++20). - Clang’s
-Wheader-hygienein the build; clang-tidy’sgoogle-build-using-namespaceif you want no directive anywhere, even inside a function.
More answers about the core language are in Language; the compilers and analyzers used here are on Toolchain.
Questions
Is using namespace std bad practice?
At global scope in a header, yes: SF.7 of the C++ Core Guidelines says “Don’t write using namespace at global scope in a header file”, since every file that includes it gets the directive. In a source file the Guidelines accept it: SF.6 allows it for foundation libraries such as std and calls a .cpp file “a form of local scope”, but warns it “can lead to name clashes”. These samples measured that cost: ambiguous globals, and two calls that silently ran a std function instead of yours.
What is the difference between using namespace std and using std::cout?
using namespace std; is a using-directive: it declares nothing, and lets unqualified lookup find the names of std, including ones declared later in the file, as if they were declared in the global namespace. using std::cout; is a using-declaration: it declares the one name cout in the current scope, so a variable of that name already declared there is an error at that line, and a function of yours of that name joins the overload set. The declaration takes exactly the names you list.
Does using namespace std make a program slower or bigger?
No. The directive only changes which declaration a name means when the compiler looks it up: it generates no code and adds nothing to the binary, so the program changes only if a name comes to mean another function, as in the silent std::count call.
Do I need using namespace std to use cout?
No. std is the namespace of the C++ standard library, and #include <iostream> declares std::cout in it, so writing std::cout is enough. There is no #include for a namespace: the directive is a separate line that only changes how unqualified names are looked up. With no #include at all, the directive is ill-formed, because nothing has declared std yet, but MSVC 19.44 and GCC 16.2 compiled it silently and both Clangs only warned "using directive refers to implicitly-defined namespace 'std'".
Can I add my own functions to namespace std?
No. Adding declarations or definitions to namespace std, or to a namespace inside it, is undefined behavior unless the standard says otherwise. The general exception is a specialization of a standard class template that depends on a type of your own and meets the library’s requirements. To give your type its own swap, declare it in your type’s namespace, where an unqualified swap(a, b) or std::ranges::swap finds it by argument-dependent lookup.
Is using namespace std::literals safe?
It is the exception the C++ Core Guidelines make in SF.7, the rule against using namespace at global scope in a header: user-defined literal operators of your own are spelled with an underscore, such as operator""_x, so they do not collide with the library's. std::literals and the namespaces inside it are inline, so with <string> and <chrono> included, the one directive brings in suffixes such as "tally"s for a std::string and 30s for std::chrono::seconds.
Sources
- [namespace.udir] Using namespace directiveC++ standard working draft
- [namespace.udecl] The using declarationC++ standard working draft
- [basic.lookup.argdep] Argument-dependent name lookupC++ standard working draft
- [namespace.std] Namespace stdC++ standard working draft
- Namespacescppreference.com
- SF.6: Use using namespace directives for transition, for foundation libraries (such as std), or within a local scope (only)C++ Core Guidelines
- SF.7: Don’t write using namespace at global scope in a header fileC++ Core Guidelines
- clang-tidy: google-build-using-namespaceLLVM Project