Fair enough. And discussing different opinions like this is a good way
to learn - whether we learn new ideas, or learn more about how other
people might see things, or learn something about ourselves when we are
pushed into thinking about our own opinions.
>
> I don't see your replacement code as a "solution", because I don't see
> the shorter version as a problem. Expanding one line to six can make
> sense in some cases. I just don't see this as one of those cases.
I have a preference towards spacing things out rather than having a
compact representation. That's a preference, not an absolute - there
can be many overriding factors, and the "best" choice at any given time
can vary significantly. (I think we can ignore the choice of brace
style for now - that's a different matter.)
Some of the reasons for usually preferring a layout with an "if" and
separate assignments, rather than a combined assignment, include:
1. It is clear when a given variable is assigned to. I believe it is
important to know what can be changed by a piece of code, and where each
piece of data can be changed. Along with this goes a bias towards
having multiple return value functions return structs (or tuples) rather
than taking a pointer to an variable to change. In my opinion it makes
it easier to see the data flow.
2. It is easier to change code if you later need to do other things in
connection with the conditional (i.e., if you want to do something else
when "x == y" other than assign "c" to "a").
3. A code layout with separate statements and avoiding doing too many
things in one line works much better with tools for version control or
comparison (diff tools) - it is easier to see what has changed and what
has not changed.
4. In my line of programming, C is dominant - the proportion of C++
coding is increasing, but still much less than C. And a sizeable
proportion of small-systems embedded programmers have a background from
hardware and electronics, rather than some kind of computer science
degree. This gives a different viewpoint and different experiences,
which can be a good thing and a bad thing (teams with mixed backgrounds
are useful). But it means that you sometimes have to be careful with
coding techniques that are relatively uncommon - there is a balance to
be found between confusing other people and having an opportunity to
teach new ideas. The kind of coding you use can be very different for a
dedicated team of higher-level, experienced C++ coders and when you are
working with one or two people who cover a broad range of tasks from
electronics design through to simple programming tasks. That does not
mean that "lowest common denominator" programming is the right tactic,
but you may have to have more justification before using a technique
that has a high chance of being unfamiliar.
5. Code needs to be tested and debugged. When dealing with
small-systems embedded work, techniques like simulation or unit testing
are often impractical or impossible - much of the close-to-the-metal
coding can only be tested in-system. Spacing out the code more makes it
far easier add breakpoints, logging, volatile variables, and other
debugging aids.
6. Sometimes code must be commented. I'm a great fan of expressing
things in code rather than in comments, by use of good names, clear
code, static asserts, etc., but comments are useful. With compact code,
it is rarely possible to put comments close to the action.
7. Sometimes the results from more compact form are significantly less
efficient. A quick test suggests that gcc on x86 will give the same
object code for an "if" with separate assignments as "(x == y ? a : b) =
c;". But the C equivalent, "*(x == y ? &a : &b) = c;", is massively
inefficient for local variables that would otherwise reside in
registers. (gcc is usually quite good at keeping local variables in
registers even if you take their address.) In the embedded world, some
compilers are not very good at optimising - I have seen the results of
conditional operator expressions produce very poor code even in the
right-hand side of expressions.
8. Coding standards often limit such expressions, whether or not a
particular programmer might think it is a good idea. If an embedded
programmer is working to the MISRA standards (which is common in the
industry), then even "c = (x == y ? a : b);" is not allowed - it must be
written "c = ((x == y) ? a : b);" or "c = (x == y) ? a : b;", and there
are limits to the complexities of the expressions "a" and "b".
>
> In my humble opinion, the biggest barrier to understanding
>
> (x == y ? a : b) = c;
>
> is knowing that a conditional expression can appear on the LHS of an
> assignment, i.e., that it can be an lvalue. Any C or C++ programmer
> should already understand that the LHS of an assignment is an expression
> that's evaluated to determine what object is to be assigned to. Once
> you realize that a conditional expression can be an lvalue, the meaning
> **IMHO** becomes obvious.
I think it is fairly obvious what the expression does - though not
obvious that it is allowed by the language (particularly because it is
allowed in C++ but not in C). However, just because it could only mean
one thing does not make it easy to follow - and if the reader is using
more effort to understand the mechanics of what the code is doing, they
have less brain power available for the important part - /why/ the code
is doing what it does.
>
> Of course in real code you very probably wouldn't call the variables
> x, y, a, b, and c. With meaningful names, I suspect the intent of the
> code would be clearer.
Indeed - and I think we all agree that context and names are vital, and
that the clearest way to express something in code depends on wider
circumstances and cannot be covered by simple fixed rules.