Consider this topic a sequel of the following topic:
Previous installment Undefined behavior and sequence points
Let's revisit this funny and convoluted expression (the italicized phrases are taken from the above topic *smile* ):
i += ++i;
We say this invokes undefined-behavior. I presume that when say this, we implicitly assume that type of i
is one of the built-in types.
What if the type of i
is a user-defined type? Say its type is Index
which is defined later in this post (see below). Would it still invoke undefined-behavior?
If yes, why? Is it not equivalent to writing i.operator+=(i.operator++());
or even syntactically simpler i.add(i.inc());
? Or, do they too invoke undefined-behavior?
If no, why not? After all, the object i
gets modified twice between consecutive sequence points. Please recall the rule of thumb: an expression can modify an object's value only once between consecutive "sequence points. And if i += ++i
is an expression, then it must invoke undefined-behavior. If so, then its equivalents i.operator+=(i.operator++());
and i.add(i.inc());
must also invoke undefined-behavior which seems to be untrue! (as far as I understand)
Or, i += ++i
is not an expression to begin with? If so, then what is it and what is the definition of expression?
If it's an expression, and at the same time, its behavior is also well-defined, then it implies that the number of sequence points associated with an expression somehow depends on the type of operands involved in the expression. Am I correct (even partly)?
By the way, how about this expression?
//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!
a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.
You must consider this too in your response (if you know its behavior for sure). :-)
Is
++++++i;
well-defined in C++03? After all, this is this,
((i.operator++()).operator++()).operator++();
class Index
{
int state;
public:
Index(int s) : state(s) {}
Index& operator++()
{
state++;
return *this;
}
Index& operator+=(const Index & index)
{
state+= index.state;
return *this;
}
operator int()
{
return state;
}
Index & add(const Index & index)
{
state += index.state;
return *this;
}
Index & inc()
{
state++;
return *this;
}
};
s
is user-defined type!)
It looks like the code
i.operator+=(i.operator ++());
Works perfectly fine with regards to sequence points. Section 1.9.17 of the C++ ISO standard says this about sequence points and function evaluation:
When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function.
This would indicate, for example, that the i.operator ++()
as the parameter to operator +=
has a sequence point after its evaluation. In short, because overloaded operators are functions, the normal sequencing rules apply.
Great question, by the way! I really like how you're forcing me to understand all the nuances of a language that I already thought I knew (and thought that I thought that I knew). :-)
http://www.eelis.net/C++/analogliterals.xhtml Analog literals comes to my mind
unsigned int c = ( o-----o
| !
! !
! !
o-----o ).area;
assert( c == (I-----I) * (I-------I) );
assert( ( o-----o
| !
! !
! !
! !
o-----o ).area == ( o---------o
| !
! !
o---------o ).area );
As others have said, your i += ++i
example works with the user-defined type since you're calling functions, and functions comprise sequence points.
On the other hand, a[++i] = i
is not so lucky assuming that a
is your basic array type, or even a user defined one. The problem you've got here is that we don't know which part of the expression containing i
is evaluated first. It could be that ++i
is evaluated, passed off to operator[]
(or the raw version) in order to retrieve the object there, and then the value of i
gets passed to that (which is after i
was incremented). On the other hand, perhaps the latter side is evaluated first, stored for later assignment, and then the ++i
part is evaluated.
++i
, and the reading of i
on the RHS of the assignment, then the order would be unspecified. Because one of the allowable orderings does those two things with no intervening sequence point, behavior is undefined.
a
and builtin i
.
I think it's well-defined:
From the C++ draft standard (n1905) §1.9/16:
"There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function13) . Several contexts in C++ cause evaluation of a function call, even though no corresponding function call syntax appears in the translation unit. [ Example: evaluation of a new expression invokes one or more allocation and constructor functions; see 5.3.4. For another example, invocation of a conversion function (12.3.2) can arise in contexts in which no function call syntax appears. — end example ] The sequence points at function-entry and function-exit (as described above) are features of the function calls as evaluated, whatever the syntax of the expression that calls the function might be. "
Note the part I bolded. This means there is indeed a sequence point after the increment function call (i.operator ++()
) but before the compound assignment call (i.operator+=
).
Alright. After going through previous responses, I re-thought about my own question, particularly this part that only Noah attempted to answer but I'm not convinced with him completely.
a[++i] = i;
Case 1:
If a
is an array of built-in type. Then what Noah said is correct. That is,
a[++i] = i is not so lucky assuming that a is your basic array type, or even a user defined one . The problem you've got here is that we don't know which part of the expression containing i is evaluated first.
So a[++i]=i
invokes undefined-behavior, or the result is unspecified. Whatever it is, it's not well-defined!
PS: In above quotation, strike-through is of course mine.
Case 2:
If a
is an object of user-defined type which overloads the operator[]
, then again there are two cases.
If the return type of overloaded operator[] function is built-in type, then again a[++i]=i invokes undefined-behavior or the result is unspecified. But if the return type of overloaded operator[] function is user-defined type, then the behavior of a[++i] = i is well-defined (as far as I understand), since in this case a[++i]=i is equivalent to writing a.operator[](++i).operator=(i); which is same as, a[++i].operator=(i);. That is, assignment operator= gets invoked on the returned object of a[++i], which seems be very well-defined, since by the time a[++i] returns, ++i have already been evaluated, and then the returned object calls operator= function passing the updated value of i to it as argument. Note that there is a sequence point between these two calls. And the syntax ensures that there is no competition between these two calls, and operator[] would get invoked first, and consecutively, the argument ++i passed into it, would also get evaluated first.
Think of this as someInstance.Fun(++k).Gun(10).Sun(k).Tun();
in which each consecutive function call returns an object of some user-defined type. To me, this situation seems more like this: eat(++k);drink(10);sleep(k)
, because in both situations, there exists sequence point after each function call.
Please correct me if I'm wrong. :-)
k++
and k
are not separated by sequence points. They can both be evaluated before either Sun
or Fun
are evaluated. The language only requires that Fun
is evaluated before Sun
, not that Fun
's arguments are evaluated before Sun
's arguments. I'm kind of explaining the same thing again without being able to provide a reference, so we're not going to progress from here.
Sun
executes, but Fun
's argument ++k
may be evaluated before or after that. There are sequence points before and after Fun
executes, but Sun
's argument k
may be evaluated before or after that. Therefore, one possible case is that both k
and ++k
are evaluated before either Sun
or Fun
are evaluated, and so both are before the function-call sequence points, and so there is no sequence point separating k
and ++k
.
eat(i++);drink(10);sleep(i);
? ... even now, you could say i++
may be evaluated before or after that?
k
and ++k
. In the eat/drink example, there is as sequence point between i
and i++
.
eat()
and sleep()
exists sequence point(s), but between there arguments doesn't even one. How can arguments to two function calls separated by sequence points, belong to the same sequence points?
Success story sharing