Back to Home

Using in C++: name lookup and repeated declarations

The article analyzes the behavior of using directives and declarations in C++: repeated in namespace and blocks, restrictions in classes, differences in name lookup. Code examples with unexpected behavior are provided.

Using pitfalls in C++: why names deceive the compiler
Advertisement 728x90

Using Directives and Declarations in C++: Hidden Name Lookup Pitfalls

In namespace scopes, repeated using declarations for the same name are allowed and do not cause conflicts. The compiler silently ignores duplicates, treating them as harmless repetitions.

namespace lib {
    int value = 42;
}

namespace demo {
    using lib::value;
    using lib::value; // Valid, no new entity created

    void f() {
        value = 10; // Refers to lib::value
    }
}

This rule evolved from C++98, where such repetition was formally prohibited in block scopes, yet compilers (GCC, MSVC) accepted it anyway. CWG Issue 36 updated the standard in C++03 to align behavior with namespaces.

Block Scopes: Historical Asymmetry

In functions or block scopes, repeated using of the same name previously triggered a compilation error under C++98 §7.3.3/9:

Google AdInline article slot
void f() {
    using A::i;
    using A::i; // Error in C++98 standard
}

Compiler practice outpaced the standard—this code worked in real-world builds. The committee acknowledged the inconsistency, drawing parallels with permissible repeated function declarations:

void f() {
    void g();
    void g(); // Well-formed
}

Since C++03, repeated using declarations in block scopes are permitted if the names are identical.

Limitations in Classes

using declarations in classes only work for names from base classes—not from external namespaces:

Google AdInline article slot
struct S {
    using lib::value; // Error: non-member name in class scope
};

This is a fundamental restriction: using declarations do not directly import external names into a class scope.

Difference Between using namespace and using Declaration

The key distinction lies in how name lookup operates:

  • using namespace expands the search scope without introducing names into the current scope.
  • using declaration introduces a local name within the current scope.

The following example illustrates a conflict:

Google AdInline article slot
namespace B { int x = 10; }
namespace C { int x = 20; }
namespace A {
    using namespace B; // Directive: extends lookup scope

    void f() {
        using C::x; // Declaration: introduces x locally

        A::x = 1; // B::x (qualified lookup via using namespace)
        x = 2;    // C::x (unqualified lookup, finds local name)
    }
}

Inside f(), the two assignment statements modify different variables: A::x (which resolves to B::x) and the local x (which refers to C::x). Unqualified lookup stops at the first match in the current scope.

Full test case:

int main() {
    A::f();
    printf("A::x: %d\n", A::x); // 1
    printf("B::x: %d\n", B::x); // 1
    printf("C::x: %d\n", C::x); // 2
}

Practical Implications for Code Reviews

These constructs make code harder to understand. In large functions, using declarations can obscure the true origin of names, leading to runtime bugs. Recommendation: avoid using in production code unless explicitly documented.

At interviews, 80% of mid-to-senior developers fail to explain this behavior without debugging—indicating a lack of hands-on experience with name lookup.

Key Takeaways:

  • Repeated using declarations for the same name are allowed in namespaces and block scopes since C++03.
  • using in classes is restricted to names from base classes.
  • using namespace expands lookup scope but doesn’t introduce names; using declaration creates a local name.
  • Unqualified name lookup stops at the first match in the current scope.
  • These patterns are red flags during code reviews.

— Editorial Team

Advertisement 728x90

Read Next