views:

270

answers:

6

Greetings, everyone!

Examining my own code, I came up to this interesting line:

const CString &refStr = ( CheckCondition() ) ? _T("foo") : _T("bar");

Now I am completely at a loss, and cannot understand why it is legal. As far as I understand, const reference must be initialized, either with r-value or l-value. Uninitialized references cannot exist. But ()? operator executes a CheckCondition() function before it assigns value to the reference. I can see now, that while CheckCondition() is executed, refStr exists, but still not initialized. What will happen if CheckCondition() will throw an exception, or pass control with a goto statement? Will it leave the reference uninitialized or am I missing something?

+6  A: 

You are missing something - it is completely legal code, and in fact such code is one of the commonest and best uses of the conditional operator. It's always a mistake to think that the compiler must internally do things in the same order that the code is layed out on the page - it is perfectly at liberty to evaluate the conditional operator (which is justv another expression) and then use the result to perform the initialisation.

As for a goto, there is no way of using one in an initialisation. And if an exception is thrown, the reference is deemed never to have been created in the first place.

anon
+2  A: 

I can see now, that while CheckCondition() is executed, refStr exists, but still not initialized.

From a language lawyer point of view, this is wrong. During initialization, refStr doesn't exist yet. I'd guess that your visual debugger is giving you misleading hints.

If the code inside the initialization leads to an error condition, refStr will not exist, and will not ever have existed.

Konrad Rudolph
+2  A: 

This is completely legal. Either this finishes successfully and the reference is bound to a valid object or an exception is thrown and control is transferred outside the block and the reference is no longer in scope so noone cares of it anymore.

sharptooth
A: 

An exception will bring you to a place where refStr isn't accessible and you can't go to a place where it is from there. A goto won't be able to get out of CheckCondition() if it is a function, and you won't be able to use a goto if it is a macro. A longjmp() will have the same effect as an exception: you'll go to a place where refStr isn't accessible.

AProgrammer
+4  A: 

Simpler example: const int x = foo();

This constant too has to be initialized, and for that foo() needs to be called. That happens in the order necessary: x comes into existance only when foo returns.

To answer your additional questions: If foo() would throw, the exception will be caught by a catch() somewhere. The try{} block for that catch() surrounded const int x = foo(); obviously. Hence const int x is out of scope already, and it is irrelevant that it never got a value. And if there's no catch for the exception, your program (including const int x) is gone.

C++ doesn't have random goto's. They can jump within foo() but that doesn't matter; foo() still has to return.

MSalters
Got it now. Throwing an exception will leave the reference's scope, leaving the reference unreachable. "x comes to existance only when foo returns" - is it so? What about const int x = foo(x)?
SadSido
const int x = foo(x) was an example I considered mentioning but didn't. It exposes an edge case where the lifetime of an object (time domain) doesn't match the lookup scope (source code domain). The problem is that the name lookup of x in foo(x) does refer to the const int, but the object doesn't exist yet at that moment. And the language lawyering needed to explain what happens in these cases is complex, eg. it depends on the declaration scope.
MSalters
Well, existance and lifetime aren't the same, anyway. Consider this code: `struct A { A() { /* object exists, but hasn't started lifetime yet */ } } a;` Lifetime for A starts after the constructor has completed, but surely, the object exists already when the constructor starts execution. Lifetime is merely a property of objects, which says whether the object is alive or not - not whether it exists or not. Funnily, the Standard defines "Lifetime" for objects, but says "The lifetime of a reference is its storage duration" - but references aren't objects.
Johannes Schaub - litb
Neither does it define what storage a local "int " uses. "a" in this case is not a variable, and it's not the declaration of an object. Instead, it says that whether or not it uses storage is unspecified. So, whether the reference started lifetime is unspecified, after all. I wonder what consequences that has. Whether the Standard at all in some paragraph relies on the lifetime of a reference, or the lifetime of an entity that can either be an object or reference.
Johannes Schaub - litb
I found that `12.2/5` in the draft actually refers to the lifetime of a reference, when it specifies how long a temporary bound to a reference persists. I think this scares me, because it basically means that whether or not the temporary in this case persists is unspecified: `string const` i think i will create a defect report about it.
Johannes Schaub - litb
What's the precise problem? Remember that the temporary is an unnamed std::string. "hello" has type char[6] so the reference can't bind to it directly. And the lifetime of the temporary std::string is extended to that of the reference.
MSalters
@MSalters, i lawyered, and found there is a potential defect. The code is surely intended to work, but it looks like the Standard misses to define the storage duration of a local reference.
Johannes Schaub - litb
@litb I don't see the problem either. Isn't the reference acting like a kind of autopointer, albeit one managed by the compiler rather than by user code? I find "The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persistsfor the lifetime of the reference except as specified below." fairly explicit, ignoring the "below" stuff. Or is the "below" stuff what concerns you?
anon
+3  A: 

Uninitialized references cannot exist.

Unfortunately funny things can be done during initialization. You could have also written

const int& a = foobar(a) ? 1 : 2;

or for the matter

const int& a = a;

I suppose as the compiler proceeds from left to right, a is indeed in scope on the right side, so technically you should be able to use it and at best it can warn:

"ComeauTest.c", line 9: warning: variable "a" is used before its value is set

  const int& a = foobar(a) ? 1 : 2;
                        ^

Naturally this can only result in undefined behavior as with using any uninitialized variable.

Your example is fine, since you don't use the reference before it has been initialized.

UncleBens
+1 for mention of undefined behaviour.
Konrad Rudolph
+1 Never thought it is possible...
SadSido
One thing we should keep in mind... While doing `const int ` is undefined behavior, because you evaluate `a` not having set it before, the following is not undefined behavior, and shows a case where referring to the variable just being initialized is valid: `void *p = `. No value is read, and no undetermined lvalue is evaluated, in this case.
Johannes Schaub - litb
Yup, same thing as `const int S = sizeof(S);` This of course becomes even funnier with C++0x : `auto S = sizeof(S);`.
MSalters