Scope in C++ , a tutorial ?

 

What is a scope ?

A scope is where an identifier is declared , it can be thought of , as containing a list of identifiers . There are multiple scopes in C++ , hence multiple places , where identifiers can be declared .

An identifier can only be defined once in the same scope . An identifier in C++ , is just a a name which can be given , for example , for a namespace , for a class , for a function , for a variable … In different scopes , identifiers with the same name can be defined .

There are six scopes , in C++ . They are , the file scope also known as the global scope ; the namespace scope , the class scope , the block scope also known as the local scope ; the function scope , and the prototype scope . So in these scopes , identifiers can be declared . Scopes , can be nested , in one another .

name lookup , is the act of searching for an identifier in the different accessible scopes .

The file scope , or the global scope

The file scope , or the global scope , is also called the translation unit scope . A translation unit , is the resulting source code , after having performed preprocessor directives , such as #include , or #define .

An identifier declared in the translation unit scope , or file scope , is declared outside any other scope .

/* file :  myMath.h */

int add(int a , int b ){
  return a + b ;}

float add(float a, float b){
  return a + b ;}
/* add , is an identifier declared in 
   the file scope , for a function , 
   which is overloaded .*/


/* file :  myProg.cpp */

#include "myMath.h" 
/*Includes the file 
  myMath.h .
  After preprocessing , 
  a single translation unit ,
  or source code is created . 
  The global identifiers are : 
  add , var_i , var1_i , 
  var_f , var1_f , main .*/

int var_i  = 0;
int var1_i = 1;

float var_f = 1.0f;
float var1_f = 3.0f;

int main(void ){
  int var1_i = 3;
  int result_i = add(var_i , var1_i );
  /*add var_i which is equal to 0 , with
    var1_i , which is equal to 3  ,
    the result 3 , is stored , in result_i .*/

  float var1_f = -1.0f;
  float result_f = add(var_f , ::var1_f );
  /*add var_f which has a value of 1.0f ,
    to the variable  qualified 
    by the global scope var1_f .
    ::var1_f has a value of 3.0f . 
    The result is 4.0f , and is stored in 
    var1_f .*/ }

An identifier declared in the global scope , is visible and can be accessed , starting the point after which it has been declared , from within the global scope , or from within any nested scope .

For example , var_i in the previous example , is visible in the scope delimited by the curly braces after void ) , hence its value was used when calling the function add .

As a rule , identifiers declared in parent scopes , are visible within nested scopes . In name lookup , the nested scope is searched first , and parent scopes are also searched . Name lookup starts from the scope where an identifier is used .

So in the previous example , the value of var1_i is 3 , because var1_i is used in the scope delimited by the curly brackets after void ) , where a call to the add function is performed , hence name lookup starts in this scope .

It can be specified where the name lookup is to be performed , by using the scope resolution operator :: . When an identifier is qualified by a scope , only the qualified by scope is searched for this identifier .

Hence in the previous example , ::var1_f , is qualified with the global scope , the global scope is searched for the identifier var1_f . ::var1_f has a value of 3.0f .

The namespace scope

A namespace is used to create scopes . A scope holds declaration of identifiers , and can prevent name clashing . Defining an identifier with the same name , can be done in different scopes . A scope definition can be expanded .

int xid_i = 1 ; 

namespace OTHER_ID{
  int xid_i = 5 ;}

namespace OTHER_ID{
  int zid_i = 5 ;}

float tax(float money ){
  return money * 0.15f ;}

namespace irregular{
  float tax(float money ){
    return money * 0.20f ;}}

namespace discounted{
  float tax(float money ){
    return money * 0.10f ;}}

namespace {
  char d_c = '1';
  char l_c = 'u'; }


int main(void ){

  char var_c = ::d_c;
  /*The identifier d_c is
    preceded with the global 
    scope operator :: .
    d_c is declared in an unnamed 
    namespace . Identifiers in an
    unnamed namespace belong as
    accessible to the global scope . 
    Hence the value , of the 
    variable var_c , is the 
    character '1' .*/


  var_c  = l_c ; 
  /*The identifier l_c is used in
    the current scope , a lookup 
    for the identifier l_c takes 
    place in the current scope . 
    It is not found , hence the enclosing 
    scope , which is the global scope , 
    is searched . l_c is declared in 
    an unnamed namespace , as such it 
    belongs to the file scope as 
    accessible , hence the value of
    the variable var_c is 
    the character 'u' .*/


  tax(20 );
  /*tax is called without using the
    scope operator . Hence  the scope
    where the tax identifier is used ,
    is first searched , and after that
    other accessible scopes . 
    The tax function is defined in the global, 
    scope , hence the returned value is 
    3.0f .*/


  irregular::tax(20 );
  /*tax is qualified , using the scope 
    operator :: by the namespace irregular , 
    the tax identifier is searched in ,
    the namespace irregular . The 
    tax function defined in irregular , 
    is called . The result is 4.0f .*/


  using irregular::tax ; 
  tax(20 );
  /*The using declaration , 
    using irregular::tax is used .
    The using declaration , adds the 
    tax identifier , to the current 
    scope . An identifier  with the same 
    name ,  must not exist in  the current
    scope , or else this will 
    cause an error .
    tax(20 ) is called , and the 
    scope operator  :: is not used , 
    hence the current scope , is 
    searched for the tax identifier . 
    The tax identifier is found in the 
    current scope, this is the identifier 
    added from the namespace irregular , 
    hence the tax function from irregular , 
    is called . The result is 4.0f .*/


  using namespace discounted;
  tax(20 );
  /*The using directive , using namespace , 
    does not add the identifiers 
    in the namespace discounted , 
    to the current scope . They 
    are only  made accessible . When
    calling tax , the current scope 
    is searched for the identifier tax .
    The identifier tax is added , 
    by the using declaration , 
    using irregular::tax , precedingly , 
    to the current scope , hence what is
    called is the  tax function from , 
    the irregular namespace , which 
    returns 4.0f , and not 2.0f .*/


  using namespace OTHER_ID;
  int varID_i = xid_i ;
  /*This statement will cause , 
    a compiler error to occur . 
    The using directive , using namespace 
    , makes accessible all the identifiers 
    in the namespace OTHER_ID , in the 
    current scope .  The identifiers , 
    from the global scope , are also accessible
    from the current scope . Hence ambiguity ,
    to which identifier is meant arise .*/ }

The class scope

Identifiers declared in a class , have class scope . The scope operator :: can be used to access a member of a scope . When used with a class , it can be used to access or initialize static variables . It can also be used to access methods of a class , in order to provide a definition .

#include<iostream>

class Foo{
  /*Declare The class
    Foo .*/
public :
  int fct();
  /*Declare Foo's public
    method fct .*/
private :
  static int var;
  /*Declare Foo's private
    static variable var .*/};

int Foo::var = -1;
/*Initialize Foo private
  static variable var .*/

int Foo::fct(){
  /*Define Foo function fct .*/
  return var;}

int main(void ){
  Foo foo;
  std::cout << foo.fct() << std::endl;
  /*Output :
   -1 . */}

The local scope

A local scope , starts and ends with the curly brackets {} . Local scopes can be nested . When defining a function , the function parameters are part of the local scope immediately following its parameters list . Declaration in a for loop , are also part of the local scope immediately following the for loop.

#include<iostream>

int add(int x , int y );
/*x , y belong to the function ,
  prototype scope .*/

int main(){
  std::cout << add(1 , 3 ) << std::endl;
  std::cout << add(3 , 1 ) << std::endl ;
/*Output :
20
2 .*/}

int add(int x , int y ){
  int z = 10;
  /*x , y , z belong to the 
    local scope created after
    the function parameters 
    list .*/
  if(x < y ){
  /*The curly brackets create a 
    nested local scope , 
    z belongs to the newly 
    create nested local
    scope .*/
     int z = 20 ; 
     return z ;
     /*Returns 20 .*/}
  else {
    int i = 0 ;
    /*i Belongs to another newly 
      create nested local
     scope.*/
    for(int i = 2 ; i < 4 ; i ++ )
    /*Creates another local scope , 
      i has a starting value of 2 .*/ 
      return i; /*returns 2 */ }}

function scope

Labels declared inside a function , have a function scope . These are the only entities that have functional scope .

#include<iostream>
void square_cube( int n ){
  int i = 0 ;
 loop:{
    if( i == n )
      return ;
    else if(i % 2  == 0 )
      goto powerTwo;
    else
      goto powerThree;

  powerTwo :
    std::cout << i * i << std::endl;
    i++;
    goto loop;
  powerThree:
    std::cout << i * i * i << std::endl;
    i++;
    goto loop ;}}

int main(void ){
  square_cube(5 ) ;}
/*output 
0
1
4
27
16*/

prototype scope

Parameters declared in a prototype declaration have a prototype scope .

bool fit(float weight);
/*weight , has prototype scope .*/

How name lookup takes place ?

Unless if qualified using the scope operator :: , an identifier is searched for starting the point it is used , as such in the scope it is used , the search progress , through the accessible scopes , as described in the previous sections . If qualified , the qualified scope , is searched for the identifier .

In addition to that , for functions which are unqualified , if the name of a function is not found in the scope in which it is used , or in the other accessible scopes , an argument dependent name lookup , also known as Koenig lookup , takes place .

Basically the scopes of the type of the arguments , are searched for the function declaration .

#include<iostream>

namespace nmspcFoo{
  class ClassFoo{};

  void printScopeName(ClassFoo arg ){
    std::cout << "nmspcFoo" << std::endl; }}

int main(void ){
  nmspcFoo::ClassFoo fooVar ;
  printScopeName(fooVar ) ;}
/*Output 
nmspcFoo .*/