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
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 i
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
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
4 responses to “Point of declaration”
“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!
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.