C++

Scope in C++

An entity in C++ has a name, which can be declared and/or defined. A declaration is a definition, but a definition is not necessarily a declaration. A definition allocates memory for the named entity, but a declaration may or may not allocate memory for the named entity. A declarative region is the largest part of a program in which the name of an entity (variable) is valid. That region is called a scope or a potential scope. This article explains scoping in C++. Furthermore, basic knowledge in C++ is needed to understand this article.

Article Content

Declarative Region and Scope

A declarative region is the largest part of a program text in which the name of an entity is valid. It is the region in which the unqualified name can be used (seen) to refer to the same entity. Consider the following short program:

#include <iostream>
using namespace std;

void fn()
    {
        int var = 3;
        if (1==1)
        {
            cout<<var<<'\n';
        }
    }

int main()
{
    fn();
    return 0;
}

The function fn() has two blocks: an inner block for the if-condition and an outer block for the function body. The identifier, var, is introduced and seen in the outer block. It is also seen in the inner block, with the cout statement. The outer and inner blocks are both the scope for the name, var.

However, the name, var, can still be used to declare a different entity such as a float in the inner block. The following code illustrates this:

#include <iostream>
using namespace std;

void fn()
    {
        int var = 3;
        if (1==1)
        {
            float var = 7.5;
            cout<<var<<'\n';
        }
    }

int main()
{
    fn();
    return 0;
}

The output is 7.5. In this case, the name, var, can no longer be used in the inner block to refer to the integer of value 3, which was introduced (declared) in the outer block. Such inner blocks are referred to as potential scope for entities declared in the outer block.

Note: An entity of the same type, like that of the outer block, can still be declared in the inner block. However, in this case, what is valid in the inner block is the new declaration and its meaning, while the old declaration and its meaning outside of the inner block remain valid in the outer block.

A declaration of the same name in an inner block normally overrides the declaration of the same name outside that inner block. Inner blocks can nest other inner blocks.

Global Scope

When a programmer just starts typing a file, that is the global scope. The following short program illustrates this:

#include <iostream>
using namespace std;

float var = 9.4;

int main()
{
    cout <<var<<'\n';
    cout <<::var<<'\n';

    return 0;
}

The output is:
9.4
9.4

In this case, the declarative region or scope for var begins from the point of declaration for var, continues downward until the end of the file (translation unit).

The block of the main() function is a different scope; it is a nested scope for the global scope. To access an entity of the global scope, from a different scope, the identifier is used directly or preceded by the scope resolution operator, :: .

Note: The entity, main(), is also declared in the global scope.

Block Scope

The if, while, do, for, or switch statement can each define a block. Such a statement is a compound statement. The name of a variable declared in a block has a block’s scope. Its scope begins at its point of declaration and ends at the end of its block. The following short program illustrates this for the variable, ident:

#include <iostream>
using namespace std;

int main()
{
    if (1==1)
        {
            /*some statements*/
            int ident = 5;
            cout<<ident<<'\n';
            /*some statements*/
        }
    return 0;
}

A variable, such as ident, declared at block scope is a local variable.

A variable declared outside the block scope and above it can be seen in the header of the block (e.g., condition for if-block) and also within the block. The following short program illustrates this for the variable, identif:

#include <iostream>  
using namespace std;

int main()
{
    int identif = 8;
 
    if (identif == 8)
        {
            cout<<identif<<'\n';
        }
    return 0;
}

The output is 8. There are two block scopes here: the block for the main() function and the nested if-compound statement. The nested block is the potential scope of the main() function block.

A declaration introduced in a block scope cannot be seen outside the block. The following short program, which does not compile, illustrates this with the variable, variab:

#include <iostream>  
using namespace std;

int main()
{
    if (1 == 1)
        {
            int variab = 15;
        }
    cout<<variab<<'\n';    //error: accessed outside its scope.

    return 0;
}

The compiler produces an error message for variab.

An entity introduced, declared in the header of a compound function, cannot be seen outside (below) the compound statement. The following for-loop code will not compile, resulting in an error message:

#include <iostream>
using namespace std;

int main()
{
    for (int i=0; i<4; ++i)
        {
            cout<<i<<' ';
        }
    cout<<i<<' ';

    return 0;
}

The iteration variable, i, is seen inside the for-loop block but not outside the for-loop block.

Function Scope

A function parameter is seen in the function block. An entity declared in a function block is seen from the point of declaration to the end of the function block. The following short program illustrates this:

#include <iostream>
#include <string>
using namespace std;

string fn(string str)
    {
        char stri[] = "bananas";
        /*other statements*/
        string totalStr = str + stri;
        return totalStr;
    }

int main()
{
    string totStr = fn("eating ");
    cout<<totStr<<'\n';

    return 0;
}

The output is:
eating bananas

Note: An entity declared outside the function (above it) can be seen in the function parameter list and also in the function block.

Label

The scope of a label is the function in which it appears. The following code illustrates this:

#include <iostream>
using namespace std;

void fn()
    {
        goto labl;
        /*other statements*/
        labl: int inte = 2;
        cout<<inte<<'\n';
    }

int main()
{
    fn();

    return 0;
}

The output is 2.

Enumeration Scope

Unscoped Enumeration
Consider the following if-block:

if (1==1)
        {
            enum {a, b, c=b+2};
            cout<<a<<' '<<b<<' '<<c<<'\n';
        }

The output is 0 1 3.

The first line in the block is an enumeration, a, b, and c are its enumerators. The scope of an enumerator begins from the point of declaration to the end of the enclosing block of the enumeration.

The following statement will not compile because the point of declaration of c is after that of a:

enum {a=c+2, b, c};

The following code segment will not compile because the enumerators are accessed after the enclosing block of the enumeration:

if (1==1)
    {
        enum {a, b, c=b+2};
    }
cout<<a<<' '<<b<<' '<<c<<'\n';  //error: out of scope

The above enumeration is described as an unscoped enumeration, and its enumerators are described as unscoped enumerators. This is because it begins only with the reserved-word, enum. Enumerations that begin with enum class or enum struct are described as scoped enumerations. Their enumerators are described as scoped enumerators.

Scoped Enumeration
The following statement is OK:

enum class nam {a, b, c=b+2};

This is an example of a scoped enumeration. The name of the class is nam. Here, the scope of the enumerator begins from the point of declaration to the end of the enumeration definition, and not the end of the enclosing block for the enumeration. The following code will not compile:

if (1==1)
    {
        enum class nam {a, b, c=b+2};
        cout<<a<<' '<<b<<' '<<c<<'\n';   //error: out of scope for enum class or enum struct  
    }

Class Scope

With normal scoping, the declarative region begins from a point, then continues and stops at a different point. The scope exists in one continuous region. With the class, the scope of an entity can be in different regions that are not joined together. The rules for nested blocks still apply. The following program illustrates this:

#include <iostream>
using namespace std;

//Base class
class Cla
    {
        private:
            int memP = 5;
        protected:
            int memPro = 9;
        public:
        void fn()
            {
                cout<<memP<<'\n';
            }
    };

//Derived Class
class DerCla: public Cla
    {
        public:
        int derMem = memPro;
    };
int main()
{
    Cla obj;
    obj.fn();
    DerCla derObj;
    cout<<derObj.derMem<<'\n';

    return 0;
}

The output is:
5
9

In the class Cla, the variable memP, is seen at the point of declaration. After that, the short portion of “protected” is skipped, then seen again in the class member function block. The derived class is skipped, then seen again at the main() function scope (block).

In the class Cla, the variable memPro, is seen at the point of declaration. The portion of the public function fn() is skipped, then seen in the derived class description block. It is seen again down in the main() function.

Scope Resolution Operator
The scope resolution operator in C++ is :: . It is used to access a static member of the class. The following program illustrates this:

#include <iostream>
using namespace std;

class Cla
    {
        public:
            static int const mem = 5;
        public:
            static void fn()
                {
                    cout<<mem<<'\n';
                }
    };
int main()
{
    cout<<Cla::mem<<'\n';
    Cla::fn();

    return 0;
}

The output is:
5
5

The static members are seen in the main() function block, accessed using the scope resolution operator.

Template Parameter Scope

The normal scope of a template parameter name begins from the point of declaration to the end of its block, as in the following code:

template<typename T, typename U>  struct Ages
    {
        T John = 11;
        U Peter  = 12.3;
        T Mary  = 13;
        U Joy   = 14.6;
    };

U and T are seen within the block.

For a template function prototype, the scope begins from the point of declaration to the end of the function parameter list, as in the following statement:

template<typename T, typename U> void func (T no, U cha, const char *str );

However, when it comes to the class description (definition), the scope can also be of different portions as in the following code:

#include <iostream>
using namespace std;

    template<class T, class U> class TheCla
        {
            public:
            T num;
            static U ch;

            void func (U cha, const char *str)
                {
                    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
                }
            static void fun (U ch)
                {
                    if (ch == 'a')
                        cout << "Official static member function" << '\n';
                }
        };

int main()
{
    TheCla<int, char> obj;
    obj.num = 12;
    obj.func('$', "500");

    return 0;
}

Name Hiding

An example of name hiding occurs when the name of the same object type is re-declared in a nested block. The following program illustrates this:

#include <iostream>
using namespace std;

void fn()
    {
        int var = 3;
        if (1==1)
            {
                int var = 4;
                cout<<var<<'\n';
            }
        cout<<var<<'\n';
    }

int main()
{
    fn();
    return 0;
}

The output is:
4
3

It’s because var in the nested block hid var in the outer block.

Possibility for Repeating Declaration in the Same Scope

The point of the declaration is where the name is introduced (for the first time) in its scope.

Function Prototype
Different entities, even of different types, cannot normally be declared in the same scope. However, a function prototype can be declared more than once in the same scope. The following program with two function prototypes and corresponding function definition illustrates this:

#include <iostream>
using namespace std;

void fn(int num);
void fn(int num);

void fn(int num)
    {
        cout<<num<<'\n';
    }

int main()
{
    fn(5);

    return 0;
}

The program works.

Overloaded functions
Overloaded functions are functions with the same name but different function signatures. As another exception, overloaded functions with the same name can be defined in the same scope. The following program illustrates this:

#include <iostream>
using namespace std;

void fn(int num)
    {
        cout<<num<<'\n';
    }

void fn(float no)
    {
        cout<<no<<'\n';
    }

int main()
{
    fn(5);
    float flt = 8.7;
    fn(flt);
   
    return 0;
}

The output is:
5
8.7

The overloaded functions have been defined in the global scope.

Namespace Scope

Namespace Scope deserves its own article. The said article has been written for this website, linuxhint.com. Just type the search words “Namespace Scope” in the search box of this site (page) and click OK, and you will get the article.

Scope in Different Portions

The class is not the only scheme where the scope can be in different portions. Friend specifier, certain uses of the elaborated-type-specifier, and using-directives are other schemes where the scope is in different places – for details, see later.

Conclusion

A scope is a declarative region. A declarative region is the largest part of a program text in which the name of an entity is valid. It can be divided into more than one portion in accordance with certain programming schemes, such as nested blocks. The portions that do not have the declaration point forms the potential scope. The potential scope may or may not have the declaration.

About the author

Chrysanthus Forcha

Discoverer of mathematics Integration from First Principles and related series. Master’s Degree in Technical Education, specializing in Electronics and Computer Software. BSc Electronics. I also have knowledge and experience at the Master’s level in Computing and Telecommunications. Out of 20,000 writers, I was the 37th best writer at devarticles.com. I have been working in these fields for more than 10 years.