On 27/06/18 22:54, Alf P. Steinbach wrote:
> On 27.06.2018 16:02, David Brown wrote:
>> On 27/06/18 15:06, Alf P. Steinbach wrote:
>>> On 27.06.2018 14:46, David Brown wrote:
>>>>
>>>> Your next challenge is to re-write it using a std::variant as a way of
>>>> combining the two distinct classes :-)
>>>
>>> That's good, I didn't think of that.
>>>
>>> But it's a library solution to what the core language already offers for
>>> this case.
>>>
>>> I don't think the ?: syntax is obscure; on the contrary, it's basic,
>>> while `std::variant` gets into the more advanced territory, as I see it.
>>>
>>
>> The ?: operator itself is standard, but unusual (compared to the other
>> operators) and programmers are often less familiar with it. In simple
>> cases it is common enough - for the sort of usage you have here, it /is/
>> obscure.
>
> How can a single ?: be obscure?
It is a matter of how you use it - it is not the ?: operator itself that
is hard. "(a < b) ? b : a" is fine. Your monstrosity is not.
>
> Beginners learn it, very early on, and you say some experienced
> programmers you've encountered find it challenging?
Beginners learn the syntax for ?:, and then a fair proportion promptly
forget about it and don't use it in normal code. However, I don't claim
many programmers would find something like the "maximum" ?: example to
be difficult. I am saying that the code /you/ wrote, with it's unusual
syntax, its use of poorly-understood lifetime rules, and unhelpful
layout is obscure, hard to understand, and easy to get wrong.
Good code is /clear/. It is clear what the code does, and what the code
does not do. The layout should reflect the logic of the code.
As to what experienced programmers would think about it - just read the
replies you have got in this thread. There are plenty of experienced
C++ programmers here - none of them think this is a good way to write
code, several have asked what it does, and others have simply not
bothered to even try to figure it out. The most positive response you
got was from me - that it is an interesting experiment, but not
something for real code.
>
> I find that claim hard to believe.
>
I am not sure what your normal programming environment is - maybe you
write your code alone. You have a very odd style, and are perhaps a
poor judge of what other people find comfortable in their coding. In
any coding review I have seen, your typical c.l.c++ code would be
rejected outright - reviewers would assume it is obvious where the
problems lie.
This does not mean your code is wrong - or even that there is a problem
with your style. It means it is significantly different from normal
styles - enough to be difficult and error-prone for many people to
interpret, and enough to be seen as different merely for the sake of
being different or appearing clever. Personally, I think it is good
sometimes to see such variation, and it can help us all learn - but I
would hate to have to /work/ with your code.
To balance this, it is clear that all programmers have a style and a
view of "normal programming styles" that is heavily influenced by the
type of work they do. That applies to me as well as anyone else.
>
>> It would also be banned by any decent coding standard - you
>> never allow short-circuit expressions that have side effects. So you
>> don't allow "A && B" if B has a side-effect - you write "if (A) { B; }".
>> The same goes for || and ?:.
>
> Yes, it's /generally/, often, maybe even usually, a Bad Idea™ to express
> side effects with boolean operators, because if anything else isn't
> evident, one expects such an expression to just compute a boolean value.
>
> But
>
> 1. it isn't /always/ bad to do that with boolean expressions, and
There are a few idioms that rely on non-execution of parts of
expression, like "p && foo(*p)". As a C or C++ programmer, you need to
be familiar with these sorts of things to understand other people's code.
That does not mean it is a good idea to /write/ them. And coding
standards that are concerned with quality code, ban them. Here are a
couple of rules from MISRA (yes, I know MISRA has its faults):
Rule 12.3: The comma operator should not be used.
Rule 13.5: The right hand operand of a logical && or || operator shall
not contain persistent side effects.
This allows things like "x = p && cos(p);", but not "p && printf(*p)".
The point is that if someone reads quickly over the code and assumes
that the right-hand operand is evaluated in some cases when it is not,
it does not affect the logic of the code.
Having said that, there can certainly be places where this kind of thing
/can/ be appropriate. Deep within library code, you sometimes need code
constructs that are unusual, complicated, or take a long time to
understand. There is plenty in the implementation of the C++ standard
library that is beyond the comprehension of most C++ programmers.
> 2. a choice expression isn't like a boolean expression.
It is in this sense.
>
> Regarding (1), in C++ the core language features are often just used to
> implement higher level features that we know from other languages. E.g.
> preprocessor directives and conventions about filenames and contents,
> are used to implement modules. In that spirit shortcut boolean
> expressions are sometimes used to implement bail-out from a sequence of
> operations that indicate success/failure via boolean returns:
>
> // A. OK for me
> const bool success = a() and b() and c() and d() and e();
>
Functions called "a", "b", etc., are not okay for me in real code - they
would have longer names. So this would have to be spread across many
lines - not okay. And real code would likely have parameters, and
probably some code between the function calls.
You are mistaking a hypothetical example with real code.
> which would be banned by your “decent coding standard”, instead of the
> more complex exception based code
>
> // B. Gah!
> bool success = true;
> try
> {
> a(); b(); c(); d(); e();
> }
> catch( exception const& )
> {
> success = false;
> }
>
> or the code that a literal interpretation of what you write, would yield:
>
> // C. Double gah, kill me.
> bool success = a();
> if( success )
> {
> success = b();
> if( success )
> {
> success = c();
> if( success )
> {
> success = d();
> if( success )
> {
> success = e();
> }
> }
> }
> }
>
> Again, compare that to
>
> // OK for me
> const bool success = a() and b() and c() and d() and e();
>
There are many other ways to do this:
bool success = true;
if (success) success &= a();
if (success) success &= b();
if (success) success &= c();
if (success) success &= d();
if (success) success &= e();
or:
bool doAll() {
if (!a()) return false;
if (!b()) return false;
if (!c()) return false;
if (!d()) return false;
if (!e()) return false;
return true;
}
const bool success = doAll();
or:
bool success = false;
do {
if (!a()) break;
if (!b()) break;
if (!c()) break;
if (!d()) break;
if (!e()) break;
success = true;
} while (false);
> So “It would also be banned by any decent coding standard - you
> never allow short-circuit expressions that have side effects.” appears
> to mean that the coding standards that you regard as decent, require the
> fantastic ugliness and awkwardness of (B) or (C) above.
No, it merely means you can imagine beauty in unrealistic examples, but
can't imagine good ways to implement real code.
>
> Regarding (2), that a choice expression isn't like a boolean expression,
> choice expressions are IME much more commonly used for side effects than
> boolean expressions are, especially where a result value is used.
>
And it would be, IME, an even worse idea to rely on side-effects and
lack thereof in a conditional expression compared to a logical one.
(If the gcc extension of statement expressions were to be included in
standard C and C++, it would allow you to express some of these things
neatly and clearly.)
>
>
>> Of course std::variant is advanced - it is C++17, and thus not available
>> (much less familiar) to many programmers. It was merely a suggestion of
>> an alternative idea for this sort of code - not because I thought it
>> would be a /good/ way to write it.
>
> Yes, considering all possibilities is often useful.
>
> If for nothing else, one might learn something from the exercise. :)
>
That is what we are doing here!
>
>> Writing fancy code like this can be a fun exercise, as long as you don't
>> mistake it for /real/ code. Being "smart" can help people learn and
>> give new ideas. Writing it in actual serious code is "smart-arse",
>> which is no longer a complement.
>
> I don't get what's fancy or “smart” about it.
>
Ah, so you haven't learned enough yet :-)
> Exactly what is fancy? Is it the conditional expressions? Do the
> programmers you know have problems with conditional expressions?
>
>
>> It's like using Greek letters as extra
>> operators in C++ classes - it's fun, but highly unlikely to be good
>> coding practice.
>
> But what is it that's Greek to you, or that you suspect would be Greek
> to most others, in the code?
I mean like making "a Π b" be an intersection operator for sets.
>
> I see nothing but basics:
>
> RAII, conditional, comma expression. Easy peasy.
>
>
>> And since people have been giving quotations, don't forget this one:
>>
>> "Everyone knows that debugging is twice as hard as writing a program in
>> the first place. So if you're as clever as you can be when you write it,
>> how will you ever debug it? " (Brian Kernighan)
>
> As a counter example, the reason that we (C++ programmers) use RAII
> isn't because RAII is easier to debug than plain if-else's and sequences
> and `goto` a common cleanup.
>
> We use it because it's easier to understand and analyze, to prove
> correctness of, due to the simple guarantees.
Yes, of course - it is better to write code with lower risks of bugs
than to write code that makes it easier to find the bugs.