3

I was debugging a program when I came across the following code I had erroneously typed similar to the following:

//Original (wrong) std::string first("Hello"); std::string second = first + second; //Instead of this (correct) std::string first("Hello"); std::string second = first + something_else; 

Obviously I wasn't trying to do this (I can't think why anyone would want to do this), but it got me thinking. It doesn't look like the original should work, and I would assume it is undefined. Indeed, this was the source of my problem.

To make the problem more general, consider the following:

SomeType a; SomeType b = a + b; 

Is the behavior undefined simply because b is not yet initialized (see this answer)?

If the behavior is undefined, then my real question is, why?

Is this only undefined for certain standard containers, like std::string, or is this undefined in a more general sense (STL classes, user-defined classes, PODs, fundamental types)?

What part of the standard applies to this?

Assume this is c++11, if necessary.

2
  • possible duplicate of Construct object with itself as reference? Commented Apr 28, 2012 at 0:43
  • @Bo Persson - I feel that is a very related question (and useful in helping to answer this), but not quite a duplicate. It is asking why this is syntactically allowed, I'm asking in what cases it is UB. Thanks for the reference, though. It helped me understand it in a different way. Commented May 1, 2012 at 15:04

3 Answers 3

6

The C++11 standard has this to say about the scope of a newly declared name:

3.3.2 Point of declaration [basic.scope.pdecl]

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below. [ Example:

int x = 12; { int x = x; } 

Here the second x is initialized with its own (indeterminate) value. — end example ]

There is similar wording in prior C++ standards.

Off the top of my head, one rationale I can think of is that the name could be used in an initializer expression that takes the address of the object.

Sign up to request clarification or add additional context in comments.

5 Comments

Another rationale is that you can use the name for type deduction, e.g. FunkyComponent c = objectManager.get_component(objectID, c); - although I'm now wondering whether that's actually defined or not.
@StuartGolodetz: It would be much better to pass the address, if type deduction is the goal. But if you just want to avoid typing the typename twice, then auto c = objectManager.get_component<FunkyComponent>(objectID); would be better.
@BenVoigt: Yes, very true about auto in C++11 :) This was a trick I found myself using a while back (specifically here: github.com/sgolodetz/hesperus2/blob/master/source/engine/core/…) - I guess it's no longer appropriate. That said, why would it be better to pass the address out of interest?
@StuartGolodetz: So you don't make a copy of an uninitialized object.
@BenVoigt: Ah ok I see what you mean. I was using a const reference parameter when I did this, which also avoids the copy.
1

Reading an uninitialized variable can lead to undefined behavior.

The standard says this:

Initializers [dcl.init]

.......

If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value.

16 Comments

It does seem self-evident to me in the case of fundamental types, but perhaps I don't understand when it comes to classes, constructors, etc. At what point is the constructor for b called? Is it called when attempting to use the + operator, or after? Or is it just irrelevant because it is undefined?
The initialization occurs after first + second is evaluated and therefore second is not initialized.
Then with that, it is evident.
indeterminate value isn't the same as UB, though.
@BenVoigt I thought that reading an indeterminate value results in UB. If it doesn't, what is the correct terminology.
|
0

The why is simple: Because the syntax is sugar. What looks like simple assignment is, infact, copy construction; the right hand of the expression is evaluated and passed to the copy constructor of the left hand.

SomeType b = a + b; 

is actually

SomeType b(a + b /*wat?*/); 

Part of the motivation behind this is RVO. Consider instead the case of

SomeType a, b; SomeType c = a + b; 

c can actually be forwarded as the temp object that a.operator+(b) uses to construct the return value.

SomeType SomeType::operator+(const SomeType& rhs) const { SomeType temp(*this); // RVO will employ `c` here instead of a 4th object. ... return temp; // yeah, let's not and say we did. } 

Note that you can take your own address:

inptr_t i = (intptr_t)&i; void* ptr = &ptr; 

http://ideone.com/GUJyio

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.