Scope

From cppreference.com
< cpplrm; | language

Each name that appears in a C++ program is only valid 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 variable introduced by a declaration 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 there is a nested block with a declaration that introduces identical name (in which case, the entire potential scope of the nested declaration is excluded from the scope of the outer declaration)

int main()
{
    int a = 0; // scope of the first 'a' begins
    ++a; // the name 'a' is in scope and refers to the first 'a'
    {
        int a = 1; // scope of the second 'a' begins
                   // scope of the first 'a' is interrupted
        a = 42;    // 'a' is in scope and refers to the second 'a'                 
    } // block ends, scope of the second 'a' ends
      //             scope of the first 'a' resumes
} // block ends, scope of the first 'a' ends
int b = a; // Error: name 'a' is not in scope

The potential scope of a name declared in an exception handler begins at the point of declaration and ends when the exception handler ends, 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 << 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 the for loop, in the condition of a for loop, in the range_declaration of a range for loop, in the init-statement of the if statement or switch statement (since C++17), in the condition of the if statement, while loop, or switch statement 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 function parameter (including parameters of a lambda expression) or of a function-local predefined variable begins at its point of declaration.

  • If the nearest 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 global 'n' interrupted,
                  // scope of the parameter 'n' begins
       int y = n); // error: default argument references a parameter

int (*(*f2)(int n))[n]; // OK: the scope of the function parameter 'n'
                        // ends at the end of its function declarator
                        // in the array declarator, global n is in scope
// (this declares a pointer to function returning a pointer to an array of 3 int

// by contrast
auto (*f3)(int n)->int (*)[n]; // error: parameter 'n' as array bound


int f(int n = 2)  // scope of 'n' begins
try // function try block
{         // the body of the function begins
   ++n;   // 'n' is in scope and refers to the function parameter
   {
      int n = 2; // scope of the local variable 'n' begins
                 // scope of function parameter 'n' interrupted 
      ++n; // 'n' refers to the local variable in this block
    }            // scope of the local variable 'n' ends
                 // scope of function parameter 'n' resumes
} catch(...) {
   ++n; // n is in scope and refers to the function parameter
   throw;
} // last exception handler ends, scope of function parameter 'n' ends
int a = n; // Error: name 'n' is not in scope

Function scope

A label (and only a label) declared inside a function is in scope everywhere in that function, in all nested blocks, before and after its own declaration.

void f()
{
   {   
       goto label; // label in scope even though declared later
label:;
   }
   goto label; // label ignores block scope
}

void g()
{
    goto label; // error: label not in scope in g()
}

Namespace scope

The potential scope of any entity declared in a namespace begins at the declaration and consists of the concatenation of all namespace definitions for the same 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 any entity declared in the global namespace scope begins at the declaration and continues to the end of the translation unit.

The scope of an entity declared in an unnamed namespace or in an inline namespace includes 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 does not end
    inline namespace inl { // scope of inl begins
      int y; // scope of y begins
    } // scope of y does not end
} // scope of i,g,j,q,inl,x,y interrupted

namespace {
    int l=1; // scope of l begins
} // scope of l does not end (it's a member of unnamed namespace)

namespace N { // scope of i,g,j,q,inl,x,y continues
    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: repeat 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 different return type
} // scope of i,g,j,q,inl,x,y interrupted

int main() {
    using namespace N; // scope of i,g,j,q,inl,x,y resumes
    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
} // scope of i,g,j,q,inl,x,y interrupted

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 and all function bodies (even if defined outside the class definition or before the declaration of the name), default arguments, exception specifications, in-class brace-or-equal initializers, contract conditions (since C++20), and all these things in nested classes, recursively.

class X {
    int f(int a = n) { // X::n is in scope inside default parameter
         return a*n;   // X::n is in scope inside function body
    }
    int g();
    int i = n*2;   // X::n is in scope inside initializer

//  int x[n];      // Error: n is not in scope in class body
    static const int n = 1;
    int x[n];      // OK: n is now in scope in class body
};
int X::g() { return n; } // X::n is in scope in out-of-class member 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, not::c is in scope inside a member function
    }
    char c; // X::c
    enum { i = 2 }; // X::i
};

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 any class members can only be used in four 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 name of an enumerator introduced in a scoped enumeration begins at the point of declaration and ends at the end of the enum specifier (in contrast, unscoped enumerators are in scope after the end of the enum specifier)

enum e1_t { // unscoped enumeration
  A,
  B = A*2
}; // scope of A and B does not end

enum class e2_t { // scoped enumeration
    SA,
    SB = SA*2 // SA is in scope
}; // scope of SA and SB ends

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 immediately at the point of declaration and continues to 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 can be used for a non-type parameter
          class U = T // T can be used for a default type
        >
class X : public Array<T> // T can be used in base class name
{
   // T can be used inside the body as well
}; // scopes of T and U end, scope of X continues

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 outer scope for the duration of its own:

typedef int N;
template< N X, // non-type parameter of type int
          typename N, // scope of this N begins, scope of::N interrupted
          template<N Y> class T // N here is the template parameter, not int
         > struct A;

Point of declaration

Scope begins at the point of declaration, which is located as follows:

For variables and other names introduced by simple declarations, the point of declaration is immediately after that name's declarator and before its initializer, if any:

unsigned char x = 32; // scope of the first 'x' begins
{
    unsigned char x = x; // scope of the second 'x' begins before the initializer (= x)
                         // this does not initialize the second 'x' with the value 32, 
                         // this initializes the second '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 within the lambda, and can
           // be correctly captured by reference, giving a recursive function
const int x = 2; // scope of the first 'x' begins
{
    int x[x] = {}; // scope of the second x begins before the initializer (= {})
                   // but after the declarator (x[x]). Within the declarator, the outer
                   // 'x' is still in scope. This declares an array of 2 int.
}

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

(since C++17)

The point of declaration of a class or template is immediately after the identifier that names the class (or the template-id that names the template specialization) appears in its class-head, and is already in scope in the list of the base classes:

// the name 'S' is in scope immediately after it appears, 
// so it can be used in the list of base classes
struct S: std::enable_shared_from_this<S> 
{
};

The point of declaration of an enumeration is immediately after the identifier that names it appears in the enum specifier or opaque enum declaration, whichever is used first:

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

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

using T = int; // point of declaration of T is at the semicolon
using T = T;   // same as T = int

The point of declaration 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, // point of declaration is at the comma, x is initialized to 13
           y = x + 1  // the enumerator x is now in scope, y is initialized to 14
         };
}

The point of declaration 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>
};

References

  • 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