What Is a Using-Statement?

A using-statement is an instruction to the compiler about name lookup. It does not create a variable, copy an object, or rename anything. It changes what the compiler is allowed to find when it meets a name you wrote without a scope in front of it.

C++ gives you two of them, and the entire lesson comes down to how much they change:

  • a using-declaration takes one specific name out of a namespace and makes it findable unqualified
  • a using-directive makes every name in a namespace findable unqualified, including names the namespace does not have yet

One of those is a scalpel. The other is a floodgate, and the rest of this lesson is about what floods in.

The Program You Have Seen Everywhere

Textbooks, tutorials, and the starter file some IDEs generate for a new project all tend to open the same way. It is worth recognising the shape, because it is the shape this lesson recommends against.

This compiles, but it is not how we write code on this platform:

#include <iostream>

using namespace std;

int main()
{
    cout << "Report generated.\n";

    return 0;
}
Report generated.

The using namespace std; line is the floodgate version, opened at file scope, aimed at the largest namespace in the language. By the end of the lesson you will be able to say precisely what it costs, rather than just repeating that it is bad.

Qualified Names and Unqualified Lookup

Every name you write is one of two kinds.

A qualified name carries its scope with it. Most often that scope is a namespace attached with the scope resolution operator :::

std::cout      // cout, qualified by namespace std
::formatReport // formatReport, qualified by the global namespace

An unqualified name carries no scope. Written on its own, cout and formatReport are unqualified names, and the compiler has to work out where they came from.

Key Concept
Namespaces are not the only thing that qualifies a name. Class names take :: in the same way, and an object reaches into itself with the member selection operators . and ->. Classes arrive in a later chapter; the important idea now is that a qualified name tells the compiler exactly one place to look, while an unqualified name starts a search.

That search is called unqualified lookup, and it is what using-statements interfere with. The compiler works outward from the scope where the name appears, and it stops at the first scope that contains a match. If that scope contains two matches, the program is ill-formed and you get an ambiguity error rather than a coin toss.

Three Ways to Reach a Name

Suppose a namespace Celsius holds a constant you want to print. You have three options, and they differ in exactly one respect: how many names they hand to unqualified lookup.

Approach Names it makes findable On a collision Lasts
Celsius::freezingPoint none cannot collide, you named the scope one use
using Celsius::freezingPoint; exactly one, chosen by you the declared name is found first and hides the rest to the end of the enclosing scope
using namespace Celsius; every name in the namespace, now and after any future update no preference, so a tie is a compile error to the end of the enclosing scope

Read the middle column top to bottom and the whole argument of this lesson is already there. Now here is each row in code.

Naming One Thing: The Using-Declaration

A using-declaration names a single member of a namespace and lets you write that member without its prefix afterwards:

#include <iostream>

namespace Celsius
{
    constexpr double freezingPoint{ 0.0 };
    constexpr double boilingPoint{ 100.0 };
}

int main()
{
    using Celsius::boilingPoint;

    std::cout << "Boils at " << boilingPoint << '\n';
    std::cout << "Freezes at " << Celsius::freezingPoint << '\n';

    return 0;
}
Boils at 100
Freezes at 0

using Celsius::boilingPoint; says: from here to the closing brace of main(), an unqualified boilingPoint means Celsius::boilingPoint. Notice what it does not do. freezingPoint lives in the same namespace and is unaffected, because a using-declaration covers one name and one name only. Wanting both means writing two declarations.

The mechanism is worth stating precisely, because it explains the behaviour in the next section. A using-declaration declares the name at the point where you wrote it. Inside main(), boilingPoint becomes a block-scope name, and ordinary scoping applies: an inner name hides an outer one. That is why a using-declaration is described as preferring its own name in a conflict. It is not a special rule for using-declarations. It is the same inner-hides-outer rule that governs every block in C++.

Naming Everything: The Using-Directive

A using-directive names a namespace instead of a member, and every name inside becomes findable without qualification:

#include <iostream>

namespace Celsius
{
    constexpr double freezingPoint{ 0.0 };
    constexpr double boilingPoint{ 100.0 };
}

int main()
{
    using namespace Celsius;

    std::cout << "Boils at " << boilingPoint << '\n';
    std::cout << "Freezes at " << freezingPoint << '\n';

    return 0;
}
Boils at 100
Freezes at 0

Same output, one line shorter, and both constants came along for free. Shown once here so you can recognise it; the rest of this lesson explains why we do not write it.

The feature exists for a historical reason. Namespaces were not part of early C++, so everything that now lives in std once sat in the global namespace, and identifiers in ordinary programs collided with standard library identifiers as the library grew. Standardisation in 1995 moved the entire library into namespace std, which was the right fix and also broke every existing program at once. Nobody was going to hand-edit millions of lines of working code to add a prefix. A single using-directive at the top of a file made the old unqualified names resolve again, and the migration became a one-line change per file.

That was a rescue operation for code written before namespaces existed. It is not a style to adopt in code written after them.

Key Concept
A using-directive does not copy names into the scope where you wrote it. It makes them findable as though they had been declared in the nearest scope enclosing both the namespace and the directive, which for using namespace std; is the global namespace. So the imported names do not sit inside your function shielding you from outer names; they line up alongside the global names and compete with them on equal footing. That single mechanical difference is the source of everything that goes wrong below.

When Two Candidates Match

Two namespaces are allowed to use the same name. That is the point of namespaces. But a using-directive for both puts both names in the same lookup scope, and lookup has no tie-breaker.

This example does not compile, on purpose:

#include <iostream>

namespace Celsius
{
    constexpr double freezingPoint{ 0.0 };
}

namespace Fahrenheit
{
    constexpr double freezingPoint{ 32.0 };
}

int main()
{
    using namespace Celsius;
    using namespace Fahrenheit;

    std::cout << freezingPoint << '\n';

    return 0;
}
s.cpp: In function 'int main()':
s.cpp:18:18: error: reference to 'freezingPoint' is ambiguous
   18 |     std::cout << freezingPoint << '\n';
      |                  ^~~~~~~~~~~~~
  • there are 2 candidates
    • candidate 1: 'constexpr const double Celsius::freezingPoint'
    • candidate 2: 'constexpr const double Fahrenheit::freezingPoint'

The compiler is not confused about what the two candidates are. It lists them. It simply has no rule that would let it choose, so it refuses.

Swap one directive for a declaration and the ambiguity disappears, because the declared name is now a block-scope name that hides the one the directive made visible:

#include <iostream>

namespace Celsius
{
    constexpr double freezingPoint{ 0.0 };
}

namespace Fahrenheit
{
    constexpr double freezingPoint{ 32.0 };
}

int main()
{
    using namespace Fahrenheit;
    using Celsius::freezingPoint;

    std::cout << freezingPoint << '\n';

    return 0;
}
0

Two namespaces of your own are easy to spot. The interesting case is the namespace you did not write and cannot inspect line by line.

std contains hundreds of short, ordinary English words, and some of them are the obvious names for a variable. left and right are stream manipulators declared by <iostream>. They are also what most people would call the two ends of a range.

This example does not compile either:

#include <iostream>

int left{ 3 };
int right{ 8 };

int main()
{
    using namespace std;

    cout << left << " to " << right << '\n';

    return 0;
}
s.cpp: In function 'int main()':
s.cpp:10:13: error: reference to 'left' is ambiguous
   10 |     cout << left << " to " << right << '\n';
      |             ^~~~
  • there are 2 candidates
    • candidate 1: 'int left'
    • candidate 2: 'std::ios_base& std::left(ios_base&)'

The diagnostic repeats itself for right. Nothing exotic happened here: two globals with sensible names, one using-directive, and a program that will not build. Writing std::cout and dropping the directive fixes it, and so does a using-declaration for std::cout alone, because neither one brings std::left into the contest.

The Failure That Compiles

An ambiguity error is a good outcome. The compiler stops, points at the line, and you fix it in a minute. The version worth fearing is the one where the compiler finds a new candidate, decides it is a better match than yours, and says nothing.

Here is a program that uses a fictional charting library. The library's namespace is written out in full so the example runs as one file; in a real project it would arrive through an #include:

#include <iostream>

namespace ChartKit
{
    constexpr int gridSpacing{ 12 };
}

long rowHeight(long lineCount)
{
    return lineCount * 18;
}

int main()
{
    using namespace ChartKit;

    std::cout << "spacing " << gridSpacing << '\n';
    std::cout << "height " << rowHeight(3) << '\n';

    return 0;
}
spacing 12
height 54

The call rowHeight(3) finds exactly one function, the global one, and 3 converts from int to long on the way in.

Now the library ships an update. You do not touch your file. All that changes is that ChartKit gained a function, which the update's release notes describe as a small addition:

#include <iostream>

namespace ChartKit
{
    constexpr int gridSpacing{ 12 };

    int rowHeight(int lineCount) { return lineCount * 24; }
}

long rowHeight(long lineCount)
{
    return lineCount * 18;
}

int main()
{
    using namespace ChartKit;

    std::cout << "spacing " << gridSpacing << '\n';
    std::cout << "height " << rowHeight(3) << '\n';

    return 0;
}
spacing 12
height 72

The program still compiles. It still runs. It prints a different number.

The rule behind that is ordinary overload resolution: when several functions could accept a call, the compiler prefers the one needing no argument conversion. The literal 3 is an int, so ChartKit::rowHeight(int) matches exactly while your rowHeight(long) needs a conversion. The new function wins, silently, and the only reason it was ever a candidate is the using-directive that made every present and future member of ChartKit findable without a prefix.

Warning
A using-directive is a standing agreement to let a namespace you do not control introduce names into your lookup. Every future release of that library is a chance for a new name to collide with one of yours, or quietly outrank it. Here the change was visible because the number was printed; inside a larger calculation, it would be a defect with no obvious cause and no line of yours to blame.

Neither failure could have happened with ChartKit::gridSpacing and an unqualified call, or with a using-declaration naming just the one member you wanted.

What a Using-Statement Covers, and For How Long

Using-statements obey scoping rules you already know.

Where you write it What it affects
inside a block that block only, ending at the closing brace
in the global namespace of a .cpp file the rest of that file, from the statement down
inside another namespace the rest of that namespace in that file
in a header every file that includes the header, which is why it is banned there

Narrower is better, because the blast radius of a collision shrinks to match. And it matters more than it sounds, because a using-statement cannot be turned off. There is no syntax for cancelling one, and no syntax for replacing one with another inside the same scope. The only control you have is choosing where it starts and where its scope ends.

That gives a workable pattern when you genuinely need short names from two places: give each one its own block.

#include <iostream>

namespace Celsius
{
    constexpr double freezingPoint{ 0.0 };
}

namespace Fahrenheit
{
    constexpr double freezingPoint{ 32.0 };
}

int main()
{
    {
        using Celsius::freezingPoint;
        std::cout << "Celsius scale freezes at " << freezingPoint << '\n';
    }

    {
        using Fahrenheit::freezingPoint;
        std::cout << "Fahrenheit scale freezes at " << freezingPoint << '\n';
    }

    return 0;
}
Celsius scale freezes at 0
Fahrenheit scale freezes at 32

Each declaration expires at its closing brace, so the second one is free to reuse the name. Written in a single scope, those two lines would be a redeclaration error. Writing Celsius::freezingPoint and Fahrenheit::freezingPoint directly would have been shorter than either.

Order Dependence, and Why Headers Are Off Limits

There is one more property of using-statements, and it is the one that rules them out of header files entirely: what a using-statement does depends on what the compiler has already seen.

A using-declaration captures the overloads that exist at the moment it appears. Later additions to the namespace are not retroactively included. The two namespace blocks below stand in for two headers that each add an overload to the same namespace, which is exactly what the compiler sees once #include has pasted them in:

#include <iostream>

namespace Ruler
{
    void mark(double position)
    {
        std::cout << "mark(double) at " << position << '\n';
    }
}

namespace Ruler
{
    void mark(int position)
    {
        std::cout << "mark(int) at " << position << '\n';
    }
}

using Ruler::mark;

int main()
{
    mark(7);

    return 0;
}
mark(int) at 7

Both overloads were declared before the using-declaration, so both are candidates, and mark(int) is the exact match for 7.

Now move the using-declaration up by one namespace block. Nothing else changes, and main() is character for character the same:

#include <iostream>

namespace Ruler
{
    void mark(double position)
    {
        std::cout << "mark(double) at " << position << '\n';
    }
}

using Ruler::mark;

namespace Ruler
{
    void mark(int position)
    {
        std::cout << "mark(int) at " << position << '\n';
    }
}

int main()
{
    mark(7);

    return 0;
}
mark(double) at 7

When the compiler reached the using-declaration, only mark(double) existed, so only mark(double) became callable unqualified. The int overload is defined, but the call never considers it, and 7 is converted to a double.

Key Concept
This example relies on function overloading: two functions may share a name in the same scope as long as their parameter lists differ, and the compiler picks between them by comparing the arguments at the call site. A later chapter covers the selection rules. All you need here is that the candidate set is fixed when the using-declaration is read.

Now recall that #include is nothing more than text substitution. A using-statement written above an #include sits above whatever that header declares, so it captures a different set of names than the same line written below. And a using-statement written inside a header is worse still, because you cannot control what a translation unit includes before yours. The same header, included in two different orders in two different files, can make the same call resolve to two different functions.

Danger
Never put a using-statement in a header file. Every file that includes the header inherits it, whether its author wanted it or not, and the effect changes with include order. This is true of using-statements inside functions defined in headers too, for exactly the same reason.

The only place a using-statement is genuinely safe is a .cpp file, below all of its #include directives, where you can see everything that was declared before it.

Choosing What to Write

There is one more cost, and it is paid by whoever reads the code next. Given a bare call to render() under a using-directive, a reader cannot tell whether it belongs to the library or to the file they are looking at. Written as Graphics::render(), the answer is in the call. The prefix is not noise, it is the one piece of information that tells you where the definition lives, and modern editors that can reveal it on hover only help the person who thinks to hover.

Best Practice
Prefer explicit qualification: write std::cout, not cout.
Avoid using-directives entirely. The one routine exception is using namespace std::literals, needed to reach the s and sv literal suffixes.
Using-declarations are acceptable in a .cpp file, below every #include, and best kept to the narrowest block that needs them.
Never write either kind in a header file.

One point of vocabulary before moving on. The using keyword has a second, unrelated job: defining type aliases, as in using Distance = double;. That is not a using-statement and carries none of the lookup problems described here. It gets its own lesson later.

Looking Forward

Two threads from this lesson continue elsewhere. Function overloading, which decided the outcome of both the rowHeight and the mark examples, gets a full treatment of its own, including the ranking the compiler applies when several candidates are viable. And unnamed namespaces, coming up shortly, solve the opposite problem to the one here: instead of exposing names to code that should not see them, they hide names inside a single translation unit so nothing outside can reach them at all.

The habit to carry forward is smaller than either. When you write a name, know which scope you are asking the compiler to search. Qualification is how you say it out loud.

Key Terminology

Using-statement: the umbrella term for a using-declaration or a using-directive, both of which change how unqualified names are looked up.

Qualified name: a name written with an associated scope, such as std::cout or ::formatReport.

Unqualified name: a name written with no scope attached, leaving the compiler to search for it.

Using-declaration: a statement such as using std::cout; that makes one named member of a namespace usable unqualified, declaring it in the scope where the statement appears.

Using-directive: a statement such as using namespace std; that makes every member of a namespace findable unqualified, with no preference over competing names.

Unqualified lookup: the search the compiler performs for an unqualified name, working outward scope by scope and stopping at the first scope containing a match.

Ambiguity error: the compile error produced when a single scope offers two equally good candidates for one name.

Summary

What using-statements are: instructions that change unqualified name lookup. They create nothing and rename nothing.

Qualified versus unqualified: a qualified name carries its scope (std::cout, ::formatReport) and names exactly one place to look. An unqualified name (cout) starts a search.

Using-declaration: names one member, as in using std::cout;. It declares that name where you wrote it, so ordinary inner-hides-outer scoping applies and it wins against names from outer scopes. It runs to the end of its enclosing scope, and you need one per name.

Using-directive: names a whole namespace, as in using namespace std;. Every member becomes findable, including members added by future versions of the library, and the imported names compete on equal footing with names already in scope rather than being preferred or preferring.

Ambiguity: two candidates in one lookup scope is a compile error, not a coin toss, and std supplies hundreds of ordinary words such as left and right that can become the second candidate.

Silent behaviour change: a library update that adds a better-matching overload can capture a call that used to reach your function, changing results without a single warning.

Scope: inside a block, a using-statement covers that block. At namespace scope in a .cpp file, it covers the rest of the file. It cannot be cancelled or replaced, so the only control you have is where you put it.

Order dependence: a using-declaration captures the overloads visible at that point, which makes its meaning depend on include order. That is why using-statements belong in .cpp files below all #include directives, and never in headers.

The practice: qualify explicitly, avoid using-directives, keep using-declarations narrow and out of headers. The std:: prefix costs five characters and tells every future reader exactly where the name came from.