3

Why does the following code compile?

class Demo
{
    public:
    Demo() : a(this->a){}
    int& a;
};

int main()
{
    Demo d;
}

In this case, a is a reference to an integer. However, when I initialize Demo, I pass a reference to a reference of an integer which has not yet been initialized. Why does this compile?

This still compiles even if instead of int, I use a reference to a class which has a private default constructor. Why is this allowed?

lesserfish
  • 35
  • 4
  • 7
    There is plenty of C++ code that will compile without any problems but which results in undefined behavior, if executed. Just because a C++ program compiles is never a guarantee that it will not [make demons fly out of your nose](http://www.catb.org/jargon/html/N/nasal-demons.html). – Sam Varshavchik Dec 06 '21 at 00:07
  • 3
    Compiling with warnings will let you know there's something obscure here: `reference 'a' is not yet bound to a value when used here [-Wuninitialized]` (https://godbolt.org/z/MnfvKTrqW, https://godbolt.org/z/Pbcs1cPWa) – rturrado Dec 06 '21 at 00:12
  • Allowing a variable (or, in this case, a class member) to be initialised using itself has its uses, and detecting cases where it doesn't make sense is either difficult for a compiler to implement or opinion-based (there are plenty of cases where some programmers insist it's a good idea, and other programmers adamantly deny that). In terms of the standard, this particular case falls into the category of "undefined behaviour, no diagnostic required" but other cases aren't like that. – Peter Dec 06 '21 at 00:15
  • `int& i = i;` is also allowed for `int i = i` is inherited from c. If I correctly remember VC does not allow it. Maybe there is an option to turn it on/off. – zdf Dec 06 '21 at 00:17
  • @Peter I see! Do you mind expanding on what those uses might be? – lesserfish Dec 06 '21 at 00:20
  • @zdf You're right!! Did not expect that. – lesserfish Dec 06 '21 at 00:21
  • 1
    @rturrado You're right! My compiler said nothing! I guess I'll try to enable a more verbose way to compile! – lesserfish Dec 06 '21 at 00:23
  • 1
    If you are a beginner, you should enable warnings. If you are a seasoned veteran, you'll enable warnings in your sleep. If you are anything in between, you should enable warnings. – paddy Dec 06 '21 at 00:26
  • @lesserfish There are plenty - and, as I said, the merits or otherwise of them are matters of opinion and debate. But consider a function `SomeType initialise_and_register(SomeType &x)` which stores the address of its argument somewhere for later usage, and returns a value. One possible usage of that would be `SomeType foo = initialise_and_register(foo);`. Even getting away from a debate about functions that store pointers to their arguments in global storage, a certain percentage of programmers would prefer that usage over (say) `SomeType foo; foo = initialise_and_register(foo);` – Peter Dec 06 '21 at 01:49

3 Answers3

6

Why does this compile?

Because it is syntactically valid.

C++ is not a safe programming language. There are several features that make it easy to do the right thing, but preventing someone from doing the wrong thing is not a priority. If you are determined to do something foolish, nothing will stop you. As long as you follow the syntax, you can try to do whatever you want, no matter how ludicrous the semantics. Keep that in mind: compiling is about syntax, not semantics.*

That being said, the people who write compilers are not without pity. They know the common mistakes (probably from personal experience), and they recognize that your compiler is in a good position to spot certain kinds of semantic mistakes. Hence, most compilers will emit warnings when you do certain things (not all things) that do not make sense. That is why you should always enable compiler warnings.

Warnings do not catch all logical errors, but for the ones they do catch (such as warning: 'Demo::a' is initialized with itself and warning: '*this.Demo::a' is used uninitialized), you've saved yourself a ton of debugging time.

* OK, there are some semantics involved in compiling, such as giving a meaning to identifiers. When I say compiling is not about semantics, I am referring to a higher level of semantics, such as the intended behavior.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
  • Thanks. I ended up enabling all compiler warnings and had to spend like 30 minutes fixing things haha! But in the end, it seems to be best practice! – lesserfish Dec 06 '21 at 03:06
3

Why does this compile?

Because there is no rule that would make the program ill-formed.

Why is this allowed?

To be clear, the program is well-formed, so it compiles. But the behaviour of the program is undefined, so from that perspective, the premise of your question is flawed. This isn't allowed.

It isn't possible to prove all cases where an indeterminate value is used, and it isn't easy to specify which of the easy cases should be detected by the compiler, and which would be considered to be too difficult. As such, the standard doesn't attempt to specify it, and leaves it up to the compiler to warn when it is able to detect it. For what it's worth, GCC is able to detect it in this case for example.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

C++ allows you to pass a reference to a reference to uninitialized data because you might want to use the called function as the initializer.

Sniggerfardimungus
  • 11,583
  • 10
  • 52
  • 97