Skip to content
C++
Language
since C++98
Basic

Using Declaration

Brings a single name from a namespace or base class into the current scope, giving precise control over which names are visible and accessible.

Using Declarationsince C++98

A using declaration introduces exactly one name from a namespace or base class into the current declarative region, making it usable without qualification.

Overview

A using declaration has two distinct roles that share the same using keyword but serve different purposes:

  1. Namespace form β€” lifts a single name from a namespace into the current scope
  2. Class member form β€” exposes a specific base class member (or all its overloads) in the derived class scope, or adjusts its access level

The critical distinction from a using directive (using namespace std;) is precision. A using directive floods the current scope with every name in the target namespace β€” often hundreds of names in std. A using declaration names exactly one entity. In production code, and especially in headers, using declarations are almost always the correct choice.

C++11 extended using declarations to cover constructor inheritance. C++17 added the ability to expand parameter packs in using declarations, enabling the overload pattern.

Syntax

cpp
// Namespace form
using qualified-name;

// Class member form (inside a class definition)
using base-class::member-name;

// Inheriting constructors (C++11)
using base-class::base-class;

// Pack expansion in class scope (C++17)
using ...pack;

The qualified name must be accessible at the point of the declaration. For namespace members, the name must already exist in the namespace before the using declaration. For class members, the declaration must appear in a derived class and the name must be accessible in the base (public or protected).

Examples

Namespace using declarations

The idiomatic use is selecting specific names from std without polluting scope:

cpp
#include <iostream>
#include <string>
#include <vector>

using std::cout;
using std::string;
using std::vector;

void process(const vector<string>& items) {
    for (const auto& item : items) {  // auto: C++11
        cout << item << '\n';
    }
}

This is standard practice in implementation files. In header files, avoid using namespace directives entirely; a using declaration inside a function body is safe in headers if a name is needed locally.

Unhiding base class overloads

When a derived class declares a function with the same name as a base class function, it hides all base class overloads of that name β€” not just the one with matching parameters. A using declaration reintroduces the entire overload set:

cpp
struct Base {
    void log(int value);
    void log(double value);
    void log(const std::string& msg);
};

struct Derived : Base {
    using Base::log;     // reintroduce all three overloads from Base

    void log(bool flag); // adds a fourth overload without hiding Base's
};

Derived d;
d.log(42);     // Base::log(int)
d.log(3.14);   // Base::log(double)
d.log(true);   // Derived::log(bool)
d.log("msg");  // Base::log(const std::string&)

Without using Base::log, every call except d.log(true) would fail to compile β€” the derived class's log(bool) declaration hides the inherited overloads entirely.

Adjusting access level

A using declaration in a derived class can widen (but not narrow) the access of an inherited member:

cpp
class Engine {
protected:
    void reset_state();
    void flush_buffers();
};

class PublicEngine : private Engine {  // private inheritance hides everything
public:
    using Engine::reset_state;   // selectively re-expose as public
    // flush_buffers remains inaccessible
};

PublicEngine e;
e.reset_state();  // OK: promoted to public through the using declaration

Attempting to narrow access β€” placing a public base member in a private using declaration β€” is ill-formed.

Inheriting constructors (C++11)

Without constructor inheritance, every constructor in a base class must be individually forwarded in the derived class. C++11 eliminates this boilerplate:

cpp
struct Widget {
    Widget(int id, std::string label);
    Widget(int id, std::string label, bool visible);
};

// C++11 and later
struct StyledWidget : Widget {
    using Widget::Widget;  // inherit all Widget constructors

    // Additional constructors can still be declared
    StyledWidget() : Widget(0, "default") {}

private:
    int style_flags_ = 0;
};

StyledWidget sw(42, "toolbar");         // delegates to Widget(int, string)
StyledWidget sv(7, "panel", true);      // delegates to Widget(int, string, bool)

The inherited constructors behave as if declared in the derived class. explicit, default arguments, and exception specifications are all preserved. Derived-class member initializers run as normal β€” only the parameter-passing logic is delegated to the base.

Watch out for uninitialised members. If the derived class adds data members without default member initialisers, an inherited constructor leaves them indeterminate:

cpp
struct Risky : Widget {
    using Widget::Widget;
    int extra_;  // no initialiser β€” indeterminate after inherited ctor runs
};

Add a default member initialiser (int extra_ = 0;) or write the constructors explicitly.

Pack expansion in class scope (C++17)

The overload pattern β€” commonly used to visit a std::variant with multiple lambdas β€” requires expanding a parameter pack inside a using declaration:

cpp
// C++17
template<typename... Ts>
struct Overloaded : Ts... {
    using Ts::operator()...;  // expose operator() from every base
};

template<typename... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;  // deduction guide

std::variant<int, double, std::string> v = 3.14;

std::visit(Overloaded{
    [](int i)                { std::cout << "int: "    << i; },
    [](double d)             { std::cout << "double: " << d; },
    [](const std::string& s) { std::cout << "str: "    << s; }
}, v);

Without using Ts::operator()..., each base class lambda's operator() would hide the others, producing a type that accepts only the last lambda's argument type.

Best Practices

Prefer using declarations over using directives in all non-trivial code. using namespace std; at file scope in a header silently injects hundreds of names into every translation unit that includes it. A collision anywhere in a large project produces errors that trace back to an innocuous-looking header line.

Scope them tightly. A using declaration inside a function body affects only that function. There is rarely a reason to place one at namespace scope in a header.

Use using Base::Base by default for thin wrappers. Inheriting constructors ensure the derived class automatically gains any new constructors added to the base in the future, keeping the two in sync without maintenance overhead.

Always audit sibling overloads when overriding. Whenever a derived class declares a function that shares a name with any base class function, check whether the other overloads need to be reintroduced. The compiler will not warn you that they are hidden.

Common Pitfalls

Confusing declarations with directives. Both using std::cout; and using namespace std; make cout usable unqualified, but the directive also pulls in every other name in the namespace. The declaration is surgical; the directive is a blunt instrument.

Uninitialised members via inherited constructors. A derived class member without a default member initialiser is left indeterminate when an inherited base constructor runs. This is a frequent source of undefined behaviour in wrapper classes.

Ambiguity with multiple inheritance. Writing using Base1::foo; using Base2::foo; for the same name foo from two different bases produces an ambiguous overload set. Any call to foo on the derived class is ill-formed. You must provide a disambiguating definition in the derived class:

cpp
struct D : Base1, Base2 {
    using Base1::foo;
    using Base2::foo;

    void foo(int x) { Base1::foo(x); }  // explicit disambiguation required
};

Access narrowing is ill-formed. private: using Base::publicMember; is a hard compile error, not a silent runtime restriction. Access can only be maintained or widened, never narrowed, through a using declaration.

Inheriting Constructors In Depth (C++11)

The using Base::Base; form is syntactic sugar for declaring forwarding constructors in the derived class. The Standard treats inherited constructors as if they were implicitly declared in the derived class with the same parameter list, the same explicit specifier, and the same exception specification as the corresponding base constructor.

Everything gets inherited. All constructors of the base are candidates: default constructors, copy and move constructors, converting constructors, and explicit constructors. There is no mechanism to inherit a subset; if you need to suppress one, write a matching constructor in the derived class that either delegates or is explicitly = deleted.

cpp
struct Base {
    explicit Base(int x);
    Base(double y, bool flag = false);
    Base(const Base&) = default;
};

struct Thin : Base {
    using Base::Base;
    // Inherits: explicit Thin(int), Thin(double, bool=false), Thin(const Thin&)
};

Thin a(42);          // OK: explicit constructor inherited
Thin b = 42;         // Error: constructor is explicit β€” that attribute is preserved
Thin c(3.14, true);  // OK
Thin d = c;          // OK: copy constructor inherited

Default arguments are not inherited as-is. When a base constructor has default arguments, the compiler generates separate inherited constructor declarations for each viable prefix of the parameter list. Base(double, bool = false) produces both Thin(double) and Thin(double, bool) in the derived class.

Member initialization order is unaffected. When an inherited constructor runs, the derived class's own data members are initialized using their in-class member initializers (if any), in declaration order, before the constructor body executes β€” exactly as with any other constructor. The only thing delegated to the base is the computation performed by the base constructor's body. This is why adding a data member without a default member initializer is dangerous: the inherited constructor has no body to initialize it, and the member is left indeterminate.

cpp
struct Counter : Base {
    using Base::Base;

    // Safe: in-class initializer runs for every constructor including inherited ones
    int count_ = 0;

    // Dangerous: no initializer β€” indeterminate after any inherited constructor
    int raw_;
};

When inheritance fails to compile. The compiler will refuse to generate an inherited constructor if:

  • The derived class already has a constructor with the exact same signature (the user-declared one takes precedence β€” no conflict, no error; the inherited one is simply not generated).
  • The base class has a constructor that the derived class cannot use because it introduces an ambiguity with another base's constructor (multiple inheritance).
  • The inherited constructor would be implicitly deleted in the derived class because the derived class contains a member that cannot be default-constructed or copy/move-constructed as required.
cpp
struct NoDefault {
    NoDefault(int);
    // No default constructor
};

struct Problem : Base {
    using Base::Base;
    NoDefault nd_;  // no default initializer and NoDefault has no default ctor
    // All inherited constructors that don't initialize nd_ are implicitly deleted
};

Problem p(1);  // Error: deleted constructor β€” nd_ cannot be value-initialized

The fix is to supply an in-class member initializer that passes an argument: NoDefault nd_{0};.

Name Hiding: Why using Base::f Is Almost Always Needed

C++ name lookup operates by scope, not by signature. When the compiler sees d.f(args), it first searches the derived class's scope for any declaration named f. As soon as it finds one, it stops searching β€” it never looks in base class scopes. This means a single declaration in the derived class hides all overloads of that name from every base class, regardless of parameter types.

This rule has surprised C++ programmers for decades. It follows directly from how two-phase lookup works: names, not signatures, are resolved per scope.

cpp
struct Logger {
    void log(int code);
    void log(double value);
    void log(const char* msg);
    void log(const std::string& msg);
};

struct AppLogger : Logger {
    // Adds a timestamped variant β€” but hides ALL Logger::log overloads
    void log(const std::string& msg, bool timestamp);
};

AppLogger al;
al.log(42);          // Error: Logger::log(int) is hidden
al.log(3.14);        // Error: Logger::log(double) is hidden
al.log("text");      // Error: Logger::log(const char*) is hidden
al.log("text", true); // OK: AppLogger::log(string, bool)

Adding using Logger::log; repairs the overload set:

cpp
struct AppLogger : Logger {
    using Logger::log;                         // reintroduce all four overloads
    void log(const std::string& msg, bool timestamp);  // fifth overload
};

Now all five signatures coexist in AppLogger's scope and overload resolution considers all of them.

Virtual functions and name hiding interact. A using declaration in a derived class does not by itself override a virtual function β€” a proper override in the derived class body does that. However, using Base::f does make the inherited overloads visible for overload resolution, so the virtual dispatch mechanism can still select the correct final overrider.

Accessibility all-or-nothing rule. A using declaration for an overloaded name must have every overload of that name accessible. If any overload is private in the base, the using declaration is ill-formed:

cpp
class A {
private:
    void f(char);   // private
public:
    void f(int);    // public
};

class B : public A {
    using A::f;  // Error C2876: A::f(char) is inaccessible
};

When this situation arises, the only workaround is to declare individual forwarding functions for the public overloads rather than using a blanket using declaration.

Using Declaration vs Using Directive

Both forms make names usable without qualification, but they differ in mechanism, scope, and safety.

Propertyusing std::cout; (declaration)using namespace std; (directive)
Names introducedExactly oneAll names in the namespace
MechanismName injected into current scopeLookup extended to that namespace
ConflictsHard error if same name already declaredSilently ambiguous; error only on use
Header safetySafe inside function bodiesNever safe in headers
Retroactive additionsCaptures only names present at point of declarationIncludes names added later

The difference in conflict behavior is subtle but important. A using declaration that conflicts with an existing local declaration is an immediate compile error. A using directive that causes a conflict only produces an error when the ambiguous name is actually used β€” meaning the bug may sit silently until a specific code path is exercised.

The local-scope directive is the least-bad form. using namespace std; at file scope in a header is universally condemned because it poisons every translation unit that includes it. The same directive inside a function body is limited to that function and cannot escape:

cpp
#include <algorithm>
#include <vector>

void sort_descending(std::vector<int>& v) {
    using namespace std;  // Safe: scope is this function only
    sort(v.begin(), v.end(), greater<int>());
}

This is occasionally useful in test code or tightly-scoped implementation functions. It is still inferior to explicit using declarations because it captures all names retroactively as of the translation unit, including anything pulled in transitively, but the damage is bounded to one function.

Name capture timing. A using declaration binds to the overload set that exists at the point of the declaration. Names added to the namespace afterward are not included:

cpp
namespace A { void f(int); }

using A::f;           // captures only A::f(int)

namespace A { void f(char); }  // added later

f('x');  // calls A::f(int) via implicit conversion β€” A::f(char) is NOT visible here

void g() {
    using A::f;   // captures both A::f(int) and A::f(char)
    f('x');       // calls A::f(char) β€” both are now visible
}

A using directive does not have this timing sensitivity because it causes the namespace to be searched at each lookup site, picking up all names present at that point.

Pack Expansion in Using Declarations (C++17)

C++17 allows the ellipsis to appear at the end of a using declaration when the preceding name is a parameter pack. This enables a derived class to simultaneously introduce a function from every base in a pack:

cpp
template<typename... Bases>
struct MixinHost : Bases... {
    using Bases::process...;  // bring process() from every Base into scope
};

Without the pack expansion, each base class's process would hide the others (name hiding applies across all bases, not just base vs derived). The expansion generates one using declaration per element of the pack, all coexisting in the derived class's scope.

The canonical mixin pattern combines this with CRTP or plain base classes to compose behavior:

cpp
struct Serializable {
    void serialize(std::ostream& out) const;
};

struct Loggable {
    void serialize(std::ostream& out) const;  // same name, different semantics
};

// Without pack expansion this would be a problem:
// Serializable::serialize hides Loggable::serialize (or vice versa)
template<typename... Mixins>
struct Entity : Mixins... {
    using Mixins::serialize...;  // C++17: all serialize() overloads coexist
};

Entity<Serializable, Loggable> e;
// Both Entity::serialize overloads are now reachable via overload resolution

The Overloaded helper used with std::variant is the most widely seen application of this pattern:

cpp
template<typename... Ts>
struct Overloaded : Ts... {
    using Ts::operator()...;
};

// C++17 CTAD deduction guide (often needed):
template<typename... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;

Each lambda type has its own operator(). Without the pack-expanded using declaration, only the last lambda's operator() would be visible due to name hiding. The expansion makes all of them part of the same overload set.

C++20 note. Class template argument deduction (CTAD) in C++20 can synthesize deduction guides from aggregate constructors, which means the explicit deduction guide above is often no longer needed in C++20 code.

using enum (C++20)

Scoped enumerations (enum class) require qualification at every use site: Color::Red, Color::Green, Color::Blue. Inside a switch or a helper function that works entirely with one enum, this repetition degrades readability without adding information. C++20 introduces using enum EnumType; to inject all enumerators of a scoped enum into the current declarative region.

cpp
enum class Direction { North, South, East, West };

std::string describe(Direction d) {
    using enum Direction;  // North, South, East, West are now unqualified
    switch (d) {
        case North: return "north";
        case South: return "south";
        case East:  return "east";
        case West:  return "west";
    }
}

Without using enum Direction, every case label would require Direction::North etc. With it, the enumerators behave exactly like names injected by a using declaration β€” they are direct members of the enclosing scope, not just lookup hints.

Individual enumerator form. You can also import a single enumerator:

cpp
using Direction::North;  // only North is unqualified; others still need Direction::

Class-scope usage. using enum is permitted in class definitions, making the enumerators available as if they were members of the class. This is useful for implementing a class that wraps or re-exposes an enum:

cpp
enum class Severity { Info, Warning, Error, Fatal };

class Logger {
public:
    using enum Severity;   // Logger::Info, Logger::Warning, etc. are now valid
    void log(Severity s, std::string_view msg);
};

Logger l;
l.log(Logger::Error, "disk full");  // Logger::Error resolves to Severity::Error

Restrictions. The target of using enum must be a complete enum type with a reachable enumerator list (i.e., not a forward-declared enum). Dependent enum types (types that depend on a template parameter) cannot be named with using enum. Conflicting enumerator names from two simultaneous using enum declarations in the same scope produce an error at the point of the conflict, not at the point of use.

Feature detection. The macro __cpp_using_enum is defined (value 201907L) when the feature is supported.

Alias Templates (C++11)

The using keyword serves a third purpose unrelated to name import: defining type aliases, including parameterized type aliases that typedef cannot express.

Simple alias (equivalent to typedef):

cpp
using Byte   = unsigned char;          // same as: typedef unsigned char Byte;
using String = std::string;            // same as: typedef std::string String;

Alias template (no typedef equivalent):

cpp
template<typename T>
using Vec = std::vector<T>;            // Vec<int> is std::vector<int>

template<typename K, typename V>
using Map = std::map<K, V, std::less<K>, std::pmr::polymorphic_allocator<std::pair<const K, V>>>;

typedef cannot be parameterized directly. The closest pre-C++11 workaround required a struct wrapper:

cpp
// C++03 workaround
template<typename T>
struct VecHelper { typedef std::vector<T> type; };

VecHelper<int>::type v;  // verbose; requires ::type everywhere

Alias templates remove the ::type indirection. The C++14 standard library adopted this pattern throughout <type_traits>:

cpp
// C++11
typename std::remove_reference<T>::type
// C++14 alias template
std::remove_reference_t<T>          // defined as: template<T> using remove_reference_t = typename remove_reference<T>::type;

Alias templates and partial specialization. Alias templates themselves cannot be partially specialized β€” attempting to do so is ill-formed. However, an alias template can alias a class template that is partially specialized, and the alias then picks up whichever specialization the compiler selects:

cpp
template<typename T>
struct Storage { /* primary */ };

template<typename T>
struct Storage<T*> { /* pointer specialization */ };

template<typename T>
using StorageFor = Storage<T>;  // alias; picks the right specialization automatically

StorageFor<int*> s;  // selects Storage<int*> β€” the pointer specialization

Using aliases in template parameter lists. Alias templates can be passed as template template arguments, but only since C++17 when the matching rules for template template parameters were relaxed:

cpp
template<template<typename> typename Container>
void fill(Container<int>& c, int value);

fill<Vec>(v, 0);  // C++17: Vec (an alias template) matches the template template param

See Also