cpp / latest / language / scope.html /

Scope

Each name that appears in a C++ program is only visible in some possibly discontiguous portion of the source code called its scope.

Within a scope, unqualified name lookup can be used to associate the name with its declaration.

Block scope

The potential scope of a name declared in a block (compound statement) begins at the point of declaration and ends at the end of the block. Actual scope is the same as potential scope unless an identical name is declared in a nested block, in which case the potential scope of the name in the nested block is excluded from the actual scope of the name in the enclosing block.

int main() {
    int i = 0; // scope of outer i begins
    ++i; // outer i is in scope
    {
        int i = 1; // scope of inner i begins,
                   // scope of outer i pauses
        i = 42; // inner i is in scope
    } // block ends, scope of inner i ends,
      // scope of outer i resumes
} // block ends, scope of outer i ends
//int j = i; // error: i is not in scope

The potential scope of a name declared in an exception handler begins at the point of declaration and ends at the end of the exception handler, and is not in scope in another exception handler or in the enclosing block.

try {
    f();
} catch (const std::runtime_error& re) { // scope of re begins
    int n = 1; // scope of n begins
    std::cout << n << re.what(); // re is in scope
} // scope of re ends, scope of n ends
catch (std::exception& e) {
//  std::cout << re.what(); // error: re is not in scope
//  ++n; // error: n is not in scope
}

The potential scope of a name declared in the init-statement of a for loop, in the condition of a for loop, in the range_declaration of a range-for loop, in the init-statement of a if statement or switch statement (since C++17), in the condition of a if statement, switch statement, or while loop begins at the point of declaration and ends at the end of the controlled statement.

Base* bp = new Derived;
if (Derived* dp = dynamic_cast<Derived*>(bp)) {
    dp->f(); // dp is in scope
} // scope of dp ends
 
for (int n = 0; // scope of n begins
     n < 10; // n is in scope
     ++n)    // n is in scope
{
    std::cout << n << ' '; // n is in scope
} // scope of n ends

Function parameter scope

The potential scope of a name declared in a function parameter (including parameters of a lambda expression) or of a function-local predefined variable begins at the point of declaration.

  • If the enclosing function declarator is not the declarator of a function definition, its potential scope ends at the end of that function declarator.
  • Otherwise, its potential scope ends at the end of the last exception handler of the function-try-block, or at the end of the function body if a function try block was not used.
const int n = 3;
 
int f1(
    int n // scope of function parameter n begins,
          // scope of global n pauses
//  , int y = n // error: default argument references a function parameter
);
 
int (*(*f2)(int n))[n]; // OK: scope of function parameter n
                        // ends at the end of its function declarator
                        // in the array declarator, global n is in scope
// declares a pointer to function returning a pointer to an array of 3 int
 
//auto (*f3)(int n)->int (*)[n]; // error: function parameter n as array bound
 
void f(int n = 2) // scope of function parameter n begins
try // function try block
{ // function body begins
    ++n; // function parameter n is in scope
    {
        int n = 2; // scope of local n begins
                   // scope of function parameter n pauses
        ++n; // local n is in scope
    } // scope of local n ends
      // scope of function parameter n resumes
} catch (std::exception& e) {
    ++n; // function parameter n is in scope
    throw;
} // last exception handler ends, scope of function parameter n ends
 
int a = n; // global n is in scope

Namespace scope

The potential scope of a name declared in a namespace begins at the point of declaration and includes the rest of the namespace and all namespace definitions with an identical namespace name that follow, plus, for any using-directive that introduced this name or its entire namespace into another scope, the rest of that scope.

The top-level scope of a translation unit ("file scope" or "global scope") is also a namespace and is properly called "global namespace scope". The potential scope of a name declared in the global namespace scope begins at the point of declaration and ends at the end of the translation unit.

The potential scope of a name declared in an unnamed namespace or in an inline namespace includes the potential scope that name would have if it were declared in the enclosing namespace.

namespace N { // scope of N begins (as a member of global namespace)
    int i; // scope of i begins
    int g(int a) { return a; } // scope of g begins
    int j(); // scope of j begins
    void q(); // scope of q begins
    namespace {
        int x; // scope of x begins
    } // scope of x continues (member of unnamed namespace)
    inline namespace inl { // scope of inl begins
        int y; // scope of y begins
    } // scope of y continues (member of inline namespace)
} // scopes of i, g, j, q, inl, x, and y pause
 
namespace {
    int l = 1; // scope of l begins
} // scope of l continues (member of unnamed namespace)
 
namespace N { // scopes of i, g, j, q, inl, x, and y resume
    int g(char a) { // overloads N::g(int)
        return l + a; // l from unnamed namespace is in scope
    }
//  int i; // error: duplicate definition (i is already in scope)
    int j(); // OK: duplicate function declaration is allowed
    int j() { // OK: definition of the earlier-declared N::j()
        return g(i); // calls N::g(int)
    }
//  int q(); // error: q is already in scope with a different return type
} // scopes of i, g, j, q, inl, x, and y pause
 
int main() {
    using namespace N; // scopes of i, g, j, q, inl, x, and y resume
    i = 1; // N::i is in scope
    x = 1; // N::(anonymous)::x is in scope
    y = 1; // N::inl::y is in scope
    inl::y = 2; // N::inl is also in scope
} // scopes of i, g, j, q, inl, x, and y end

The name may also be visible in translation units that have imported the current translation unit.

(since C++20)

Class scope

The potential scope of a name declared in a class begins at the point of declaration and includes the rest of the class body, all the derived classes bodies, the function bodies (even if defined outside the class definition or before the declaration of the name), function default arguments, function exception specifications, in-class brace-or-equal initializers, and all these things in nested classes, recursively.

struct X {
    int f(int a = n) { // n is in scope in function default argument
        return a * n;  // n is in scope in function body
    }
    using r = int;
    r g();
    int i = n * 2; // n is in scope in initializer
//  int x[n]; // error: n is not in scope in class body
    static const int n = 1; // scope of n begins
    int x[n]; // OK: n is now in scope in class body
}; // scope of n pauses
 
struct Y: X { // scope of n resumes
    int y[n]; // n is in scope
}; // scope of n ends
 
//r X::g() {     // error: r is not in scope outside out-of-class function body
auto X::g()->r { // OK: trailing return type r is in scope
    return n;    // n is in scope in out-of-class function body
}

If a name is used in a class body before it is declared, and another declaration for that name is in scope, the program is ill-formed, no diagnostic required.

typedef int c; // ::c
enum { i = 1 }; // ::i
 
class X {
//  char v[i]; // error: at this point, i refers to ::i
               // but there is also X::i
    int f() {
         return sizeof(c); // OK: X::c is in scope in member function
    }
    enum { i = 2 }; // X::i
    char c;         // X::c
    char w[i]; // OK: i refers to X::i now
}; // scope of outer i resumes
 
typedef char* T;
struct Y {
//  T a; // error: at this point, T refers to ::T
         // but there is also Y::T
    typedef long T;
    T b;
};

Names of class members can be used in the following contexts:

  • in its own class scope or in the class scope of a derived class;
  • after the . operator applied to an expression of the type of its class or a class derived from it;
  • after the -> operator applied to an expression of the type of pointer to its class or pointers to a class derived from it;
  • after the :: operator applied to the name of its class or the name of a class derived from it.

Enumeration scope

The potential scope of an enumerator of an unscoped enumeration begins at the point of declaration and ends at the end of the enclosing scope.

The potential scope of an enumerator of a scoped enumeration begins at the point of declaration and ends at the end of the enum specifier.

enum e1_t { // unscoped enumeration
    A,
    B = A * 2 // A is in scope
}; // scopes of A and B continue
 
enum class e2_t { // scoped enumeration
    SA,
    SB = SA * 2 // SA is in scope
}; // scopes of SA and SB end
 
e1_t e1 = B; // OK: B is in scope
//e2_t e2 = SB; // error: SB is not in scope
e2_t e2 = e2_t::SB; // OK

Template parameter scope

The potential scope of a template parameter name begins at the point of declaration and ends at the end of the smallest template declaration in which it was introduced. In particular, a template parameter can be used in the declarations of subsequent template parameters and in the specifications of base classes, but can't be used in the declarations of the preceding template parameters.

template<
    typename T, // scope of T begins
    T* p,       // T is in scope
    class U = T // T is in scope
>
class X: public std::vector<T> // T is in scope
{
    T f(); // T is in scope
}; // scopes of T and U end

The potential scope of the name of the parameter of a template template parameter is the smallest template parameter list in which that name appears.

template<
    template< // template template parameter
        typename Y,    // scope of Y begins
        typename G = Y // Y is in scope
    > // scopes of Y and G end
    class T,
//  typename U = Y // error: Y is not in scope
    typename U
>
class X {
}; // scopes of T and U end

Similar to other nested scopes, the name of a template parameter hides the same name from the enclosing scope for the duration of its own.

typedef int N;
template<
    N X, // ::N is in scope
    typename N, // scope of N begins, scope of ::N pauses
    template<N Y> class T // N is in scope
>
struct A; // scope of N ends, scope of ::N resumes

Point of declaration

In general, a name is visible after the locus of its first declaration, which is located as follows.

The locus of a name declared in a simple declaration is immediately after that name's declarator and before its initializer, if any.

unsigned char x = 32; // outer x is in scope
{
    unsigned char x = x; // inner x is in scope before the initializer (= x)
                         // this does not initialize inner x with the value of outer x,
                         // this initializes inner x with its own, indeterminate, value
}
std::function<int(int)> f = [&](int n){ return n > 1 ? n * f(n - 1) : n; };
// the name of the function f is in scope in the lambda and can
// be correctly captured by reference, giving a recursive function
const int x = 2; // outer x is in scope
{
    int x[x] = {}; // inner x is in scope before the initializer (= {}),
                   // but after the declarator (x[x])
                   // in the declarator, outer x is still in scope
                   // this declares an array of 2 int
}

The locus of a class or class template declaration is immediately after the identifier that names the class (or the template-id that names the template specialization) in its class-head. The class or class template name is already in scope in the list of base classes.

struct S: std::enable_shared_from_this<S> // S is in scope at the colon
{};

The locus of enum specifier or opaque enum declaration is immediately after the identifier that names the enumeration.

enum E: int { // E is in scope at the colon
    A = sizeof(E)
};

The locus of a type alias or alias template declaration is immediately after the type-id to which the alias refers.

using T = int; // outer T is in scope at the semicolon
{
    using T = T*; // inner T is in scope at the semicolon,
                  // outer T is still in scope before the semicolon
                  // same as T = int*
}

The locus for a declarator in a using declaration that does not name a constructor is immediately after the declarator.

template<int N>
class Base {
protected:
    static const int next = N + 1;
    static const int value = N;
};
 
struct Derived: Base<0>, Base<1>, Base<2> {
    using Base<0>::next, // next is in scope at the comma
          Base<next>::value; // Derived::value is 1
};

The locus of an enumerator is immediately after its definition (not before the initializer as it is for variables).

const int x = 12;
{
    enum {
        x = x + 1, // enumerator x is in scope at the comma,
                   // outer x is in scope before the comma,
                   // enumerator x is initialized to 13
        y = x + 1  // y is initialized to 14
    };
}

The locus for an injected-class-name is immediately following the opening brace of its class (or class template) definition.

template<typename T>
struct Array
//  : std::enable_shared_from_this<Array> // error: the injected class name is not in scope
    : std::enable_shared_from_this< Array<T> > // OK: the template-name Array is in scope
{ // the injected class name Array is now in scope as if a public member name
    Array* p; // pointer to Array<T>
};

The locus of the implicit declaration for a function-local predefined variable __func__ is immediately before the function body of a function definition.

(since C++11)

The locus of a structured binding declaration is immediately after the identifier-list, but structured binding initializers are prohibited from referring to any of the names being declared.

(since C++17)

The locus of the variable or the structured bindings (since C++17) declared in the range_declaration of a range-for loop is immediately after the range_expression.

std::vector<int> x;
 
for (auto x: x) { // auto x is in scope at the closing parenthesis,
                  // vector x is in scope before the closing parenthesis
    // the auto x is in scope
}
(since C++11)

The locus of a template parameter is immediately after its complete template parameter (including the optional default argument).

typedef unsigned char T;
template<
    class T = T, // template parameter T is in scope at the comma,
                 // typedef name of unsigned char is in scope before the comma
    T // template parameter T is in scope
    N = 0
>
struct A {
};

The locus of a concept definition is immediately after the concept name, but concept definitions are prohibited from referring to the concept name being declared.

(since C++20)

The locus of a named namespace definition is immediately after the namespace name.

References

  • C++20 standard (ISO/IEC 14882:2020):
    • 6.4 Scope [basic.scope]
  • C++17 standard (ISO/IEC 14882:2017):
    • 6.3 Scope [basic.scope]
  • C++14 standard (ISO/IEC 14882:2014):
    • 3.3 Scope [basic.scope]
  • C++11 standard (ISO/IEC 14882:2011):
    • 3.3 Scope [basic.scope]
  • C++98 standard (ISO/IEC 14882:1998):
    • 3.3 Declarative regions and scopes [basic.scope]

See also

C documentation for Scope

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/scope