Point of declaration

After difficult posts about the coroutines and before yet another one I decided to take a break and write about something easier instead. This time we will have a look at one of the aspects of the declarations namely – point of declaration.

So what is this point of declaration? Intuitively it’s a point in the source code, since when the name of the declared entity is taken into account by the compiler. Usually, the point of declaration is after declarator and before initializer.

Ok, but what does it mean in practice? Standard gives us two examples of the source codes:

int i = 42;
{
  int i = i;
}

I bet, that normally most of the C++ users will guess, that value of the i in the inner scope will be 42, but most probably this is not the case. If we split the statement into parts, then int i is a declarator and = i is initializer. Since declaration point is between them, it means, that the statement is declaring the value i and assigns its own value to itself. This of course results in the undefined behavior.

Another example of the code from the standard:

const int  i = 2;
{ int  i[i]; }

Now is it undefined behavior? It sure looks strange, but again following the mentioned rule the point of declaration is in fact in the place of the ;. This means, we are declaring an array of two integers named i.

Exceptions from the general rule

There are exceptions from the general rule, which are as follows:

Classes and enums have their point of declaration immediately after their name

This makes CRTP pattern possible:

struct TheOneRight : Singleton<TheOneRing>{/*...*/};
               // ^ here is the declaration point

Aliases points of declarations are immediately after the type they point to

struct Test;
        // ^ here is declaration point

void foo(){ using Test = Test; /*does nothing*/ }
                          // ^ here is the point of declaration

Enumerators point of declaration is after its complete definition

constexpr bool yes=true, no=false;
                //^        ^here are declaration points
enum class Decision{yes = yes, no = no};
                           //^        ^ here are declaration points
enum class Incremental{first = 10, second = first+1};
                           //    ^                 ^ declaration points

Functions declaration point is before its body

void stack_eater(unsigned i){stack_eater(++i);}
                         //^ here is the declaration point  

This allows to make recursive calls.

Template parameters’ declaration points are after the parameters are introduced

using T = unsigned char;
template<class T
  = T     // lookup finds the typedef name of unsigned char
  , T     // lookup finds the template parameter T
    N = 0> struct A { };

Summary

This is the first post of the short C++ facts series, that I am going to continue. In this post, we have learned what is the point of declaration and it’s consequences on the code’s behavior. If you want to support me, there is nothing more motivating than feedback and a kind word :). You can contact me at dawid.pilarski@<mydomain>.com. Cheers!


Bibliography


4 responses to “Point of declaration”

  1. “and itโ€™s consequences on the codeโ€™s behavior” I’m not sure that you showed those consequences. Otherwise thanks for this inventory. Good to have it all in one place.

    • Hello @VicDiesel and thank you for the first comment on my blog!

      I was trying to give a hint on the codeโ€™s behavior after every example (int i=i โ€“ gives undefined behavior, declaration point of the function โ€“ allows recursive calls).

      I do agree however, that the examples are far from being comprehensive. I will try better next time.

      Cheers!

  2. A great article.
    I never thought of this, “when a symbol is considered ‘defined’”.
    This is so cool ๐Ÿ™‚
    It explains a lot of things (you enumerate them clearly) in a very clear and solid way.
    One thing I didn’t understand, is the first example?
    Why is this UB? why it is not a compilation error?
    Thank you very much for this deep overview ๐Ÿ™‚

    • @ShaulFridman Thanks for such nice words! ๐Ÿ™‚

      The reason why

      int i = i;

      is undefined behavior, is that int i; itself has indeterminate value (unless it’s a global or static variable). Now one of the features of indeterminate values is, that whenever you read such value, you end up with undefined behavior (that’s according to the C++ rules). Consider:

      int i;
      std::cout << i; because we are reading the value of i, (and its value is indeterminate) the behavior of our program is undefined. The same happens here: int i = i; we are reading the indeterminate value of i, which is undefined behavior. The reason why reading indeterminate value is UB rather than compilation error (ill-formedness) is because the compiler often doesn't have enough information on whether the value is indeterminate or not. Consider: void foo(int&); int i; foo(i); std::cout << i; // is it UB? as long as we do not know the body of the foo function, we do not know, whether the i was initialized with some value or not, so neither we nor compiler can tell whether the code should compile. But just to be clear - one of the possible behaviors of undefined behavior is compilation error ๐Ÿ™‚ so it's perfectly fine for compilers to reject the code: int i = i; but they sadly don't do that. I am not sure why, though.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.