Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Just some code

180 views
Skip to first unread message

Alf P. Steinbach

unread,
Jun 24, 2018, 1:39:37 AM6/24/18
to
I can't recall having seen this approach, so posting it here:


void cppmain()
{
using namespace std;

class With_faux_text
{
istringstream faux_input{ some_text() };
const ptr_<streambuf> original_buffer = cin.rdbuf();
public:
const char _{ '-' };
~With_faux_text() { cin.rdbuf( original_buffer ); }
With_faux_text() { cin.rdbuf( faux_input.rdbuf() ); }
};

class With_stream_detection
{
public:
const char _{ '-' };
With_stream_detection()
{
using namespace sys::io;
if( not is_connected( Stream_id::in ) ) { cin.setstate(
ios::failbit ); }
}
};

stdlib::ext::process::Command_line_args args;
(args[1] == "--faux-text"? With_faux_text{}._ :
With_stream_detection{}._), app::run();
}


Cheers!,

- Alf

Öö Tiib

unread,
Jun 26, 2018, 12:12:52 PM6/26/18
to
On Sunday, 24 June 2018 08:39:37 UTC+3, Alf P. Steinbach wrote:
> I can't recall having seen this approach, so posting it here:

I did not understand what approach you had in mind?
Was it usage of temporary r-value with life-time of whole app run?

Alf P. Steinbach

unread,
Jun 26, 2018, 3:55:25 PM6/26/18
to
Yes, I find it difficult to express cleanly as more C style code. The
original code had call to app::run() in two places. Not DRY.

Cheers!,

- Alf

guinne...@gmail.com

unread,
Jun 26, 2018, 7:30:51 PM6/26/18
to
There are so many undeclared identifiers in that code soup that I cannot understand whatever “approach” it is that you are trying to convey.

Juha Nieminen

unread,
Jun 27, 2018, 1:40:18 AM6/27/18
to
Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
> public:
> const char _{ '-' };

Is this intentional obfuscation?

Alf P. Steinbach

unread,
Jun 27, 2018, 2:24:57 AM6/27/18
to
It's a to me simplest possible and non-verbose conversion to a common
type for the two type-wise unrelated struct types, so that they can be
used in the same :?-expression:

(args[1] == "--faux-text"? With_faux_text{}._ :
With_stream_detection{}._), app::run();

I guess an alternative could be to derive them from a common base class,
but I didn't try that. The details of the rules for :? elude me. I have
to look them up every few years when I stubbornly write or by necessity
delve into some tricky :? code.

So in my humble opinion, better avoid those grayshade corners of the
language, and Keep It Super Simple™ and obviously valid. :)


Cheers!,

- Alf

David Brown

unread,
Jun 27, 2018, 3:36:58 AM6/27/18
to
It is far, far better to Keep It Simple, Stupid - and use an "if". That
rule trumps pointless DRY complaints every time:

if (args[1] == "--faux-text") {
With_faux_text w;
app::run();
} else {
With_stream_detection w;
app::run();
}

If you are really desperate to avoid duplicating app::run() here, make a
template:

template<typename T>
void run_with(T with)
{
app::run();
}

if (args[1] == "--faux-text") {
run_with(With_faux_text());
} else {
run_with(With_stream_detection());
}


Don't use ?: when you really mean an "if" statement. Don't use the
comma operator when you really mean two successive statements. There
can be rare cases when they are a neat choice - but they are very rare.

A key reason for this is that the details of ?: and the comma operator
are not well known amongst many programmers. For example, for classes A
and B, the expression "A(), B()" will create anonymous temporaries of
the two types. But do you /know/, beyond any doubt, that the temporary
A object will still be alive when B is created? The answer is that it
will - but it is not obvious to many C++ programmers.


(However, your "_" trick for converting to a common type was
interesting. The naming could be improved, but it was a neat trick.)

Juha Nieminen

unread,
Jun 27, 2018, 6:59:27 AM6/27/18
to
Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
> On 27.06.2018 07:40, Juha Nieminen wrote:
>> Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
>>> public:
>>> const char _{ '-' };
>>
>> Is this intentional obfuscation?
>
> It's a to me simplest possible and non-verbose conversion to a common
> type for the two type-wise unrelated struct types, so that they can be
> used in the same :?-expression:
>
> (args[1] == "--faux-text"? With_faux_text{}._ :
> With_stream_detection{}._), app::run();

I thought that there would be a simple explanation for that oddity,
but it appears that it's, indeed, used for even more obfuscation.

Even an experienced C++ programmer is going to have to examine
that expression for a while before he understands what's going on.
That's not good programming style.

Alf P. Steinbach

unread,
Jun 27, 2018, 7:10:31 AM6/27/18
to
The choice operator is in the language for a purpose, which happens to
be the exact purpose of the code.


> Don't use the
> comma operator when you really mean two successive statements.

In the presented code only a single statement will do.

If the comma is replaced with a ';', then cleanup will be performed be
the call to app::run() instead of after.


> There
> can be rare cases when they are a neat choice - but they are very rare.

This is one. ;-)


> A key reason for this is that the details of ?: and the comma operator
> are not well known amongst many programmers. For example, for classes A
> and B, the expression "A(), B()" will create anonymous temporaries of
> the two types. But do you /know/, beyond any doubt, that the temporary
> A object will still be alive when B is created? The answer is that it
> will - but it is not obvious to many C++ programmers.

It better be. But it's an interesting debate: at what level of knowledge
of details can one put the lines between "C++ expert", "C++ programmer"
and "programmer sometimes using C++"? I think the line between expert
and just programmer is somewhere where the programmer knows the rules of
temporaries, that knowing that basic thing does not qualify as expert.


> (However, your "_" trick for converting to a common type was
> interesting. The naming could be improved, but it was a neat trick.)

Thank you. :)

Cheers!

- Alf

bol...@cylonhq.com

unread,
Jun 27, 2018, 7:35:11 AM6/27/18
to
On Wed, 27 Jun 2018 13:10:17 +0200
Its purpose is to return 1 of 2 possible values, not to replace a multi
command "if" block.

Having seen your example code in a number of your posts I think its safe to
classify you as a showboating coder - ie you deliberately obfuscate your code
simply to try and convince everyone how clever you are. However if you try and
be too clever you can end up looking like a fool who doesn't understand the
concept of appropriate use. Your asinine "auto main() -> int" in another post
was a classic example of this.

>It better be. But it's an interesting debate: at what level of knowledge
>of details can one put the lines between "C++ expert", "C++ programmer"
>and "programmer sometimes using C++"? I think the line between expert
>and just programmer is somewhere where the programmer knows the rules of
>temporaries, that knowing that basic thing does not qualify as expert.

IMO a C++ expert is someone who cares more about the language than he does
about actually solving problems. Most of us learn enough to get the job done
and pick up more along the way from other peoples code. Very few read
stroustrup from cover to cover mainly because we're too busy doing the job
we're paid to do and have better things to do at home.

Similarly I doubt you'd get many painters obsessing about their paint brushes,
they're more concerned with the painting.

Juha Nieminen

unread,
Jun 27, 2018, 8:20:06 AM6/27/18
to
bol...@cylonhq.com wrote:
> Having seen your example code in a number of your posts I think its safe to
> classify you as a showboating coder - ie you deliberately obfuscate your code
> simply to try and convince everyone how clever you are.

"Don't be clever" - Bjarne Stroustrup

He further clarifies: "My point was to discourage overly clever code because
"clever code" is hard to write, easy to get wrong, harder to maintain, and
often no faster than simpler alternatives because it can be hard to
optimize."

Alf P. Steinbach

unread,
Jun 27, 2018, 8:36:31 AM6/27/18
to
I didn't see Boltar's comment because he's in my kill file.

And you're pretty close now, again.

Just a heads up.


- Alf

David Brown

unread,
Jun 27, 2018, 8:46:21 AM6/27/18
to
Yes it is.

> which happens to
> be the exact purpose of the code.

No it isn't.

The choice operator is part of C so that you can write things like:

x = (a < b) ? b : a;

It is part of C++ because it is part of C. It is certainly /not/ part
of the C++ language so that you can use it to do different things by
using constructors for different classes. That use is allowed because
it would be complicated to disallow it, not because it is a good idea.

>
>
>> Don't use the
>> comma operator when you really mean two successive statements.
>
> In the presented code only a single statement will do.

Logically, you meant two statements. Define a variable (from one of
your classes), then run the app.

There are lots of things you /can/ do in C or C++ - and often you can
use expressions for things that are logically separate statements. In a
fair amount of code you could replace many of the ";" with commas. No
one does, because it would be pointlessly confusing and non-idiomatic.

>
> If the comma is replaced with a ';', then cleanup will be performed be
> the call to app::run() instead of after.

It would in this particular case - but not if you wrote the code in a
more normal structure.

Your code is an interesting example of using obscure syntax - but it is
not a good way to write code.

>
>
>> There
>> can be rare cases when they are a neat choice - but they are very rare.
>
> This is one. ;-)
>

No, it is not - except from the viewpoint that it is an interesting
example. If your purpose was merely to investigate or illuminate such
possibilities, then it was clearly a good choice - if it was to write
good clear code, it failed.

>
>> A key reason for this is that the details of ?: and the comma operator
>> are not well known amongst many programmers. For example, for classes A
>> and B, the expression "A(), B()" will create anonymous temporaries of
>> the two types. But do you /know/, beyond any doubt, that the temporary
>> A object will still be alive when B is created? The answer is that it
>> will - but it is not obvious to many C++ programmers.
>
> It better be. But it's an interesting debate: at what level of knowledge
> of details can one put the lines between "C++ expert", "C++ programmer"
> and "programmer sometimes using C++"? I think the line between expert
> and just programmer is somewhere where the programmer knows the rules of
> temporaries, that knowing that basic thing does not qualify as expert.
>

Ah, there we can agree - an interesting debate could be made from this.
At what point do you restrict your coding to make it easier to
understand for a wider proportion of programmers? Or do you write code
in a more advanced way, and restrict the programmers who work with it?

(In this case, there is nothing to be gained - except a talking point
and perhaps a little knowledge - from using an obscure structure. So
here a simpler structure would be better.)

>
>> (However, your "_" trick for converting to a common type was
>> interesting. The naming could be improved, but it was a neat trick.)
>
> Thank you. :)
>

Your next challenge is to re-write it using a std::variant as a way of
combining the two distinct classes :-)


Alf P. Steinbach

unread,
Jun 27, 2018, 9:06:29 AM6/27/18
to
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.


Cheers!,

- Alf

MrBolt...@galactic.com

unread,
Jun 27, 2018, 9:41:35 AM6/27/18
to
On Wed, 27 Jun 2018 14:36:19 +0200
"Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
Thats easily fixed as you can see. And the fact that you've killfiled me
demonstrates that you're not only a show off, but have a fragile ego too and
can't handle valid criticism. I hope I never have the misfortune to have to use
any code you wrote.

>And you're pretty close now, again.

I'm sure he's quakeing in his boots.


David Brown

unread,
Jun 27, 2018, 10:02:36 AM6/27/18
to
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. 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 ?:.


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.


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. It's like using Greek letters as extra
operators in C++ classes - it's fun, but highly unlikely to be good
coding practice.


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)



James Kuyper

unread,
Jun 27, 2018, 10:21:58 AM6/27/18
to
> The choice operator is in the language for a purpose, ...

Correct. In C, it allows you to do something that otherwise requires
multiple statements in a single expression. It should only be used when
multiple statements are disallowed, such as initializers, or conditions
in #if directives or if(), while(), do while(), and for() statements, or
the first and third parts of a for(). Even in those locations, it should
only be used if it's actually clearer than the alternative (which would,
generically, involve defining a function that contains those multiple
statements).
It's in C++ for backwards compatibility, but C++ provides a number of
alternatives that render it less necessary.

> ... which happens to
> be the exact purpose of the code.

Incorrect. As a general rule in C++, anytime you might be tempted to
write an if() or switch() statement that is that is directly or
indirectly connected to a type, you should consider the possibility of
using function overloading or inheritance and a virtual member function.
I think that's precisely the right solution in this case. This also
applies if you find yourself writing multiple catch() blocks for the
same try{} block, which is explicitly type-based.

>> Don't use the
>> comma operator when you really mean two successive statements.
>
> In the presented code only a single statement will do.

Which could call an overloaded function or a virtual member function.

>> There
>> can be rare cases when they are a neat choice - but they are very rare.
>
> This is one. ;-)

No, it isn't.

Manfred

unread,
Jun 27, 2018, 12:03:54 PM6/27/18
to
You may want to keep the ?: with a std::variant :-)

Posting the code because I am not 100% confident that move ctors are
guaranteed not to be called - I'm thinking of potential move of the
std::variant.

============

#include <iostream>
#include <string>
#include <variant>


class With_faux_text
{
public:
With_faux_text(With_faux_text&&) { std::cout << "move
With_faux_text" << std::endl; }
With_faux_text() { std::cout << "ctor With_faux_text" << std::endl; }
~With_faux_text() { std::cout << "dtor With_faux_text" <<
std::endl; }
};

class With_stream_detection
{
public:
With_stream_detection(With_stream_detection&&) { std::cout << "move
With_stream_detection" << std::endl; }
With_stream_detection() { std::cout << "ctor With_stream_detection"
<< std::endl; }
~With_stream_detection() { std::cout << "dtor
With_stream_detection" << std::endl; }
};

using StreamContext = std::variant<std::monostate, With_faux_text,
With_stream_detection>;

class App
{
public:
static void run()
{
std::cout << "App::run()" << std::endl;
}
};

StreamContext createContext(int argc, char* argv[])
{
return (2 <= argc && std::string(argv[1]) == "--faux-text") ?
StreamContext{std::in_place_type<With_faux_text>} :
StreamContext{std::in_place_type<With_stream_detection>};
}

int main(int argc, char *argv[])
{
StreamContext context = createContext(argc, argv);

App::run();

}


==============

./a.out --faux-text
ctor With_faux_text
App::run()
dtor With_faux_text

Alf P. Steinbach

unread,
Jun 27, 2018, 3:46:02 PM6/27/18
to
On 27.06.2018 15:41, MrBolt...@galactic.com wrote:
> snip

Your third identify to be killfiled by me.

Alf P. Steinbach

unread,
Jun 27, 2018, 4:54:28 PM6/27/18
to
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?

Beginners learn it, very early on, and you say some experienced
programmers you've encountered find it challenging?

I find that claim hard to believe.


> 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
2. a choice expression isn't like a boolean expression.

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();

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();

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.

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.



> 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. :)


> 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.

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 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.

I am not sure about the debuggability of the code as I wrote it, though.
Maybe the ?: expression and comma expression should be written with each
part on its own line, maybe that would help a debugger show what's being
executed. That research will have to wait though (for me)...


Cheers!,

- Alf

Alf P. Steinbach

unread,
Jun 27, 2018, 4:56:43 PM6/27/18
to
Well, let's compare your virtual member function code to the one I
presented, when the codes do the same with same exception guarantees.

Cheers!

- Alf

Juha Nieminen

unread,
Jun 28, 2018, 1:13:00 AM6/28/18
to
Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
You are going to killfile me because I'm quoting Bjarne Stroustrup?

David Brown

unread,
Jun 28, 2018, 4:39:49 AM6/28/18
to
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.

PoorOldAlFS...@sadsack.com

unread,
Jun 28, 2018, 5:23:00 AM6/28/18
to
On Wed, 27 Jun 2018 21:45:47 +0200
"Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
It really makes little odds, I write my posts in vi and editing the From:
line takes seconds. You might end up with a very big kill file. Assuming you
actually use one and its not all just bluster.


Manfred

unread,
Jun 28, 2018, 7:59:33 AM6/28/18
to
On 6/28/2018 10:39 AM, David Brown wrote:
> On 27/06/18 22:54, Alf P. Steinbach wrote:
>> // 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();

Since &= is a bitwise operator, I'd rewrite this as:

bool success = true;
success = success && a();
success = success && b();
success = success && c();
success = success && d();
success = success && e();

But still I'd prefer Alf's expression, possibly split across multiple lines:
const bool success = a() and
b() and
c() and
d() and
e();

Which incidentally keeps the const qualifier (which is something I value)

Alf P. Steinbach

unread,
Jun 28, 2018, 10:53:26 AM6/28/18
to
On 28.06.2018 10:39, David Brown wrote:
> 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.


Well, let's investigate at what point you, or the programmers you
imagine, lose comprehension.

1. (s == "blah"? 1 : 2)
2. (s == "blah"? DerivedA{} : DerivedB{})
3. (s == "blah? A{}.x : B{}.x)

Has the conditional turned into a somewhat incomprehensible monstrosity
already in 2, or is it the use of a data member, as in 3, that makes
their eyes glaze over?

I hadn't ever dreamed that current programmers have such difficulties.
But I'm learning. And maybe we can nail this more precisely. ;-)


> [snip]

Cheers!,

- Alf

Bart

unread,
Jun 28, 2018, 2:03:12 PM6/28/18
to
I think this is the expression involved:

(args[1] == "--faux-text"? With_faux_text{}._ :
With_stream_detection{}._), app::run();

When I first glanced at this, I assumed it was in this form:

(A ? B : C, D);

With the D, as I thought, belonging to the C as I assumed that this was
one ?: term only, not a ?: term then something else. Actually it's like
this:

(A ? B : C), D;

That is, it evaluates B or C then executes D. At least, you do use
parentheses around the ?: which is rare in C code.

That your version is confusing might be something to do with it using 18
punctuation tokens, 22 punctation characters, 5 underlines, 3 hyphens,
and long indentifier names that look more like comments.

Now, ?: is used when you want to evaluate either x or y, and need to
immediately use the result of that within an expression. If you don't
need to use the actual value, just the side-effects, then you shouldn't
use ?:, as you would just being using it to avoid writing a proper
if-else statement.

The same argument applies to the comma operator; you use it when you
really need to evaluate one expression inside another, and not just to
be able to write:

X, Y;

instead of:

{X; Y;}

If you apply those guidelines, then your example becomes:

if (args[1] == "--faux-text")
With_faux_text{}._;
else
With_stream_detection{}._);

app::run();

Now you have form and indentation to help you see the structure. This
could with ?: and comma too:

(args[1] == "--faux-text"
? With_faux_text{}._ :
: With_stream_detection{}._),
app::run();

but it's not as good as just writing it properly.

--
bart

David Brown

unread,
Jun 28, 2018, 4:10:58 PM6/28/18
to
I think you'll have a lot of difficulty trying to get a precise point
here - this is not a binary issue. The "maximum" example is easy, your
original code is hard, and in between there is a sliding scale. It
takes experience and a good understanding of other programmers to be
able to tell if code is /too/ hard - and it will depend massively on the
details of the code, the type of problem you are solving, and of course
the programmers who are looking at the code. There is no black and
white answer here - sorry.


Alf P. Steinbach

unread,
Jun 29, 2018, 12:08:14 AM6/29/18
to
In this case one really needs to evaluate one expression inside another.

The control structure expresses exactly the intent.


>    X, Y;
>
> instead of:
>
>    {X; Y;}
>
> If you apply those guidelines, then your example becomes:
>
>     if (args[1] == "--faux-text")
>         With_faux_text{}._;
>     else
>         With_stream_detection{}._);
>
>     app::run();

Here cleanup is performed before `app::run()`, which means it will never
get executed with faux text.


> Now you have form and indentation to help you see the structure. This
> could with ?: and comma too:
>
>     (args[1] == "--faux-text"
>         ? With_faux_text{}._ :
>         : With_stream_detection{}._),
>     app::run();
>
> but it's not as good as just writing it properly.

So it seems that /formatting/ is crucial. Thanks.


Cheers!,

- Alf

woodb...@gmail.com

unread,
Jun 29, 2018, 12:44:19 AM6/29/18
to
Agreed, but iirc Nicolai Josuttis told of a problem he ran
into with RAII. He had a lock but didn't give it a name.
So the scope of the lock started and ended on the same line.
If he can be tripped up by RAII and temporaries this way,
it's not always easy breezy.


Brian
Ebenezer Enterprises - In G-d we trust.
https://github.com/Ebenezer-group/onwards

David Brown

unread,
Jun 29, 2018, 3:12:42 AM6/29/18
to
On 29/06/18 06:08, Alf P. Steinbach wrote:
> On 28.06.2018 20:03, Bart wrote:
>> Now you have form and indentation to help you see the structure. This
>> could with ?: and comma too:
>>
>> (args[1] == "--faux-text"
>> ? With_faux_text{}._ :
>> : With_stream_detection{}._),
>> app::run();
>>
>> but it's not as good as just writing it properly.
>
> So it seems that /formatting/ is crucial. Thanks.
>

I would not go as far as to say the change in formatting makes the code
good, but it certainly makes it a lot clearer. It also gives spots for
adding comments if that helps. (I do not advocate comments as an
alternative to writing clear code - but sometimes code has to be
complicated, and comments can then help.)

Even something as simple as changing the spacing can greatly improve the
readability of code.

Öö Tiib

unread,
Jun 30, 2018, 5:44:31 AM6/30/18
to
On Wednesday, 27 June 2018 17:02:36 UTC+3, David Brown wrote:
>
> 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. 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 ?:.

I also think that Alf went far in his tricks of ?: and comma usage.
However that requirement in C++ coding standards does often hurt clarity
of code.

We have lot of different unavailable states in language. Few examples
are "one past back", nullptr, NaN and std::optional. The diversity of
unavailable states indicates that it is next to impossible to write
programs without that. In place where lot of things can be unavailable
but usage of short-circuiting && for availability checks is forbidden
by standard because something potentially short-circuited might have
side effects then the resulting pile of "ifs" will not improve clarity
in any way.

Alf P. Steinbach

unread,
Jun 30, 2018, 2:02:42 PM6/30/18
to
On 24.06.2018 07:39, Alf P. Steinbach wrote:
> [snip]
>

As a compromise between succinct DRY code and what this thread says
current average programmers can grok at a glance (namely, not single
line expressions that involve all of RAII, conditional and comma) I
landed on the following more debugger-friendly but more verbose:


void cppmain()
{
using namespace std;

class With_faux_text
{
istringstream faux_input{ some_text() };
const ptr_<streambuf> original_buffer = cin.rdbuf();
public:
With_faux_text() { cin.rdbuf( faux_input.rdbuf() ); }
~With_faux_text() { cin.rdbuf( original_buffer ); }
};

class With_stream_detection
{
public:
With_stream_detection()
{
using namespace sys::io;
if( not is_connected( Stream_id::in ) ) { cin.setstate(
ios::failbit ); }
}
};

stdlib::ext::process::Command_line_args args;
if( args[1] == "--faux-text" )
{
With_faux_text{}, app::run();
}
else
{
With_stream_detection{}, app::run();
}
}


Is this, too, too complex for average current programmers?

Or would it be better to replace the commas with variable names, and if
so, what variable names would/could facilitate at-a-glance grokability?


Cheers!,

- Alf

Tim Rentsch

unread,
Jun 30, 2018, 7:51:26 PM6/30/18
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:

> I can't recall having seen this approach, so posting it here:
>
> [..code was here..]

Interesting idea. Perhaps a little weird, but definitely
interesting!

Here is a somewhat different means of arranging the cleanup.
Note the single constructor call for 'Later it' below:

#include <iostream>
#include <string>
#include <functional>
#include <sstream>

using namespace std;

using Fixer = function< void() >;

class Later {
Fixer f;
public:
Later( Fixer f0 ) : f( f0 ) {}
~Later(){ f(); }
};

int
main( int argc, char *argv[] ){
Fixer supersede_with( const char * ), use_stdin();
bool faux = argc > 1 && string( argv[1] ) == "--faux-text";
cout << " With" << (faux ? "" : "out") << " other input:\n";

Later it( faux ? supersede_with( "Your ad here!" ) : use_stdin() );
cout << " (here we are running the app)\n";
}

Fixer
supersede_with( const char *other_input ){
cout << " 'supersede_with' starting - " << other_input << '\n';
istringstream in{ basic_string<char>( other_input ) };
streambuf *s{ cin.rdbuf() };
cin.rdbuf( in.rdbuf() );

return [=](){
cin.rdbuf( s );
cout << " 'supersede_with' ending - " << other_input << "\n";
};
}

Fixer
use_stdin(){
cout << " (any tests before using regular input...)\n";
return [](){
cout << " (we have been using regular input)\n";
};
}


Disclaimer: I am certainly not an expert in C++ lambdas. AFAICT
the above code is okay, but I confess my efforts to decipher what
the C++ standard says could not be called entirely successful.
Running the program produced the expected output in both cases.

(Also thanks due to Manfred, from whose program I cribbed a bit.)

Tim Rentsch

unread,
Jul 1, 2018, 2:07:29 AM7/1/18
to
James Kuyper <james...@alumni.caltech.edu> writes:

> On 06/27/2018 07:10 AM, Alf P. Steinbach wrote:
>
>> On 27.06.2018 09:36, David Brown wrote:
>>

[...]

>>> Don't use ?: when you really mean an "if" statement.
>>
>> The choice operator is in the language for a purpose, ...
>
> Correct. In C, it allows you to do something that otherwise
> requires multiple statements in a single expression. It should
> only be used when multiple statements are disallowed, such as
> initializers, or conditions in #if directives or if(), while(), do
> while(), and for() statements, or the first and third parts of a
> for().

What nonsense. There are plenty of places ?: can be used as part
of ordinary expressions that make a program shorter, simpler,
easier to read, and easier to understand.

> Even in those locations, it should only be used if it's actually
> clearer than the alternative (which would, generically, involve
> defining a function that contains those multiple statements).

The word "clearer" in this context is sufficiently vague and
subjective that this statement has almost no semantic content.

Christian Gollwitzer

unread,
Jul 1, 2018, 2:49:06 AM7/1/18
to
Am 29.06.18 um 06:08 schrieb Alf P. Steinbach:
>> If you apply those guidelines, then your example becomes:
>>
>>      if (args[1] == "--faux-text")
>>          With_faux_text{}._;
>>      else
>>          With_stream_detection{}._);
>>
>>      app::run();
>
> Here cleanup is performed before `app::run()`, which means it will never
> get executed with faux text.
>

As a non-expert C++ programmer, I could understand the intent of the
code and the intricacies of the , operator only after it was explained
here by several people. The ?: is not the problem, but in this code I
was heavily wondering how the hell can the app::run() access anything
from these with_stream... objects. The problem is the data flow via side
effects. It would have been different if it looked like

app::run(args[1] == "--faux-text" ? with_stream() : with_faux());

Or perhaps:

app App;
app.setStream(args[1] == "--faux-text" ? with_stream() : with_faux())
app.run();

And instead of with_stream() etc. modifying global objects, it should
return a stream object to be read from.

In that case it is very clear how the information passes on to app::run.
Modifying a global state from within a temporary which then sets it back
in the destructor is an example of very tricky interaction and IMHO bad
code.

Christian

Tim Rentsch

unread,
Jul 1, 2018, 3:29:21 AM7/1/18
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:

Let me offer some purely personal reactions (not just on the
above question but on the thread topic generally).

First I think the idea that ?: is somehow "too difficult" (for
some unspecified set of programmers) is overblown. If someone
can't understand the ?: operator inside of 15 minutes they
shouldn't be programming at all, full stop.

Second, the trick with doing a member access probably deserves
a comment, but nothing more than that.

Third, ditto the first remark, for the comma operator. I don't
see anything wrong with the usage of either ?: or the comma
operator in the original code.

Fourth, I didn't know the rule about lifetime of temporary
objects but it doesn't surprise me either. Let me pretend that I
was thoroughly familiar with that rule when reading the code.

Now, despite all the foregoing, I still have a funny reaction to
the original expression overall. There are several reasons that
might contribute to this feeling. One, it's unusual to see an
object constructed conditionally - normally it's either there or
it isn't. Two, even knowing the rules for the comma operator and
temporary object lifetime, it's surprising to discover how they
interact here; I might say that part of my brain "knows" that
when control gets to a comma operator, the left-hand operand
expression is "done". Three, I can't escape the feeling that
somehow the construction is "sneaky", or that it expresses what
is meant in a less direct way than it could. I'm reminded of how
I felt the first time I saw Duff's device - I understood all the
pieces just fine, but the effect of the combination was still
suprising. In the case of Duff's device there was a particular
motivation that offset the surprise factor, and I think that
was a good decision (ie, in the original circumstances). Here I
don't know what the offsetting motivation would be. The various
forces you have mentioned - RAII, factoring, etc - all are worth
observing, but none of them is absolute. I think this is one of
those rare cases where one of the "inviolable" rules needs to be
broken, because not doing that would lead to a worse result.

So fwiw, there is my own sense of the situation. Not saying
anyone else should feel the same way, just that this is how
it strikes me.

Alf P. Steinbach

unread,
Jul 1, 2018, 4:28:02 AM7/1/18
to
On 01.07.2018 08:48, Christian Gollwitzer wrote:
> Am 29.06.18 um 06:08 schrieb Alf P. Steinbach:
>>> If you apply those guidelines, then your example becomes:
>>>
>>>      if (args[1] == "--faux-text")
>>>          With_faux_text{}._;
>>>      else
>>>          With_stream_detection{}._);
>>>
>>>      app::run();
>>
>> Here cleanup is performed before `app::run()`, which means it will
>> never get executed with faux text.
>>
>
> As a non-expert C++ programmer, I could understand the intent of the
> code and the intricacies of the , operator only after it was explained
> here by several people. The ?: is not the problem, but in this code I
> was heavily wondering how the hell can the app::run() access anything
> from these with_stream... objects. The problem is the data flow via side
> effects.

This, how the code appears to you, is interesting, because the RAII
side-effect variable general technique that is problematic to you, and
presumably also to the other critics, is idiomatic except that usually
the variable is technically needlessly /named/.

This example is from ¹cppreference.com,


std::lock_guard<std::mutex> guard(g_pages_mutex);
g_pages[url] = result;


For this use the name "guard" isn't ever used for anything. It just
possibly can produce a warning from the compiler, like hey, you forgot
to use that thing... Which possible warning can be suppressed by writing


std::lock_guard<std::mutex> guard(g_pages_mutex);
(void) guard;
g_pages[url] = result;


But better, in my opinion, is to just never introduce that extraneous
misleading not-used-for-anything name in the first place:


std::lock_guard<std::mutex>{ g_pages_mutex }
, g_pages[url] = result;


So that's the technical background: it's a cleaned up version, namely,
no unused name, of a very common C++ idiom.

* * *

A technical alternative to the "data flow via side effects" where cin is
modified, could be to pass an istream reference down the call chain
instead of directly using cin in app::run and lower.

But that would be a parameter explicitly propagated down the call chain
just to support the faux text used in the software development.

That feels very wrong to me, very Microsoft-ish :(, both because it
would misleadingly communicate to readers of the code that this
generalization was there to support the ordinary purpose of the code,
and because it would add a technically unnecessary cost to every
function in the call chain just to support readability at a top level.


> It would have been different if it looked like
>
> app::run(args[1] == "--faux-text" ? with_stream() : with_faux());

As a general technique that seems to get impractical, adding unnecessary
complication.

E.g. here's the earlier cppreference example reworked to use that technique:


void set_pages_entry(
const std::string& key,
const std::string& value,
const std::lock_guard<std::mutex>& // lets caller pass object
)
{
g_pages[key] = value;
}

//...
set_pages_entry( url, result,
std::lock_guard<std::mutex>{g_pages_mutex} );


Of course the mutex locking can and probably should be moved into that
function, given that one wants to keep the function, like this:


void set_pages_entry(
const std::string& key,
const std::string& value
)
{
std::lock_guard<std::mutex>{ g_pages_mutex }
, g_pages[key] = value;
}

//...
set_pages_entry( url, result );



But now there's something eerily familiar about the function body code...

By adopting the suggested pass-it-in-a-function-call technique, and
cleaning up the resulting code, we got back where we started.

Except that now there's an added function.


> Or perhaps:
>
> app App;
> app.setStream(args[1] == "--faux-text" ? with_stream() : with_faux())
> app.run();

Not sure what you mean here. Instead of having the global state that
affects `cin` in `cin`, have the global state that affects `cin` in an
object called `app`?


> And instead of with_stream() etc. modifying global objects, it should
> return a stream object to be read from.
>
> In that case it is very clear how the information passes on to app::run.
> Modifying a global state from within a temporary which then sets it back
> in the destructor is an example of very tricky interaction and IMHO bad
> code.

Well, /that part/ is idiomatic, except for using a technically
unnecessary name that might produce a warning about being unused.

I never understood why such name is always used.

But now I understand it some, that the name, or perhaps the separation
into distinct statements that it allows, aids in code comprehension?


Cheers!,

- Alf

Notes:
¹ <url: https://en.cppreference.com/w/cpp/thread/mutex#Example>

Manfred

unread,
Jul 1, 2018, 7:35:14 AM7/1/18
to
The intent is clear, and the idiomatic background is clear too.
Still I have a few considerations:

Your approach is heavily based on the lifetime of a temporary, which,
being unnamed, is hidden from the explicit flow of the code.
In common C++ programming, the lifetime of temporaries is typically
handled as something "the compiler will take care of", and the
programmer typically wants to care little more than the upper limit of
its lifetime span: the terminating ; of the statement.

In this case (the original one, not the lock guard) you are effectively
defining an execution context for the app. This is worth a name, IMO.

Moreover, the whole context object (With_faux_text or
With_stream_detection) is hanging on the obscure '_' member - and one
has to think about the guarantee that this actually ensures that the
whole object is kept alive.

In short, your proposal works, but instead of /using/ the syntax to
express an intent (which is what programmers do), it constrains the
coding of the intent within a chosen syntax.

* * *

That said, your recalling of the lock guard raises a different
consideration - when writing:

std::lock_guard<std::mutex> guard(g_pages_mutex);

what I still find annoying is that you may forget to name the thing
(because you don't use the name afterwards), and the resulting statement:

std::lock_guard<std::mutex>(g_pages_mutex);

will compile, but be ineffective - most annoyingly, /silently/
ineffective (unless you have a compiler that can catch this stuff, and
you instruct it to do so).

[Microsoft has handled this its way, i.e with an ad-hoc solution
in C# they have a dedicated lock statement which reads:
lock(mutex_object)
{
...
}
]

So, I think you actually have a point with respect to the unnecessary
name, but not just because it is /cleaner/ code, but because since it is
unused after declaration, it can be forgotten and lead to a non-trivial
error (non trivial because compilers may miss it).

Paavo Helde

unread,
Jul 1, 2018, 1:08:45 PM7/1/18
to
On 1.07.2018 14:35, Manfred wrote:

> That said, your recalling of the lock guard raises a different
> consideration - when writing:
>
> std::lock_guard<std::mutex> guard(g_pages_mutex);
>
> what I still find annoying is that you may forget to name the thing
> (because you don't use the name afterwards), and the resulting statement:
>
> std::lock_guard<std::mutex>(g_pages_mutex);
>
> will compile, but be ineffective - most annoyingly, /silently/
> ineffective (unless you have a compiler that can catch this stuff, and
> you instruct it to do so).

This is one of the few occasions I have found macros useful. All my
mutex locks go through macros which define a lock variable, e.g:

SCOPED_LOCK(g_pages_mutex);

This has several benefits IMO:

1. Avoids stumbling upon the most vexing parse which you brought up.

2. Can easily add, remove and modify run-time checks (potentially in
debug mode compilation only) for trouble-shooting the multi-threading
issues: checking against locking a non-recursive mutex recursively,
checking against locking mutexes in inconsistent order, i.e. potential
deadlocks, monitoring the duration of locks etc. There are diagnostic
tools to achieve similar goals without source code modifications, but in
my experience they have appeared either non-effective or too slow to
accomplish anything.

3. Can portably add filename and line number in the diagnostics for
above checks.

4. The lock variable name is hidden which is fine as it does not matter
most of the time (it's needed only when one wants to wait on the mutex).








Tim Rentsch

unread,
Jul 1, 2018, 1:29:55 PM7/1/18
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:

> On 24.06.2018 07:39, Alf P. Steinbach wrote:
>
>> [snip]
>
> As a compromise between succinct DRY code and what this thread says
> current average programmers can grok at a glance (namely, not single
> line expressions that involve all of RAII, conditional and comma) I
> landed on the following more debugger-friendly but more verbose:
>
> [some reformatting for line width]
> void cppmain()
> {
> using namespace std;
>
> class With_faux_text
> {
> istringstream faux_input{ some_text() };
> const ptr_<streambuf> original_buffer = cin.rdbuf();
> public:
> With_faux_text() { cin.rdbuf( faux_input.rdbuf() ); }
> ~With_faux_text() { cin.rdbuf( original_buffer ); }
> };
>
> class With_stream_detection
> {
> public:
> With_stream_detection()
> {
> using namespace sys::io;
> if( not is_connected( Stream_id::in ) ) {
> cin.setstate( ios::failbit );
> }
> }
> };
>
> stdlib::ext::process::Command_line_args args;
> if( args[1] == "--faux-text" )
> {
> With_faux_text{}, app::run();
> }
> else
> {
> With_stream_detection{}, app::run();
> }
> }
>
>
> Is this, too, too complex for average current programmers?
>
> Or would it be better to replace the commas with variable names, and
> if so, what variable names would/could facilitate at-a-glance
> grokability?

What this suggests to me is that RAII is the wrong model to think
about what's going on here. The objects being constructed aren't
really managing a resource in the normal sense; what they are
doing is wrapping some prologue and epilogue code around the
subject code 'app::run()'. This doesn't have to be done using
classes. Another way to get the wrapping to happen is to use a
function that takes a lambda argument. To illustrate (disclaimer:
code just typed in, not compiled):

if( args[1] == "--faux-text" )
{
with_faux_text_run( [](){ app::run(); } );
}
else
{
with_stream_detection_run( [](){ app::run(); } );
}

Then, since functions are (able to be turned into) values, we can
factor and simplify further:

bool faux = args[1] == "--faux-text";
auto wrapper = faux ? with_faux_text_run : with_stream_detection_run;

wrapper( [](){ app::run(); } );

Also, I suspect the two functions will be easier to write and
more straightforward to understand than their corresponding
classes.

To me this formulation seems preferable to a version with class
construction. A weakness is it would need to protect against
exceptions being thrown. That could be done inside each function,
either with try/catch, or with a construction done inside each
function. Depending on their internals, the two functions might
choose different approaches, or in some cases not have to worry
about exceptions at all. So what we're seeing is a functional
approach that adds a layer of abstraction, to allow simplifying
the caller.

Tim Rentsch

unread,
Jul 2, 2018, 1:41:09 PM7/2/18
to
Manfred <non...@invalid.add> writes:

> [incidental lead-in]
>
> That said, your recalling of the lock guard raises a different
> consideration - when writing:
>
> std::lock_guard<std::mutex> guard(g_pages_mutex);
>
> what I still find annoying is that you may forget to name the thing
> (because you don't use the name afterwards), and the resulting
> statement:
>
> std::lock_guard<std::mutex>(g_pages_mutex);
>
> will compile, but be ineffective - most annoyingly, /silently/
> ineffective (unless you have a compiler that can catch this stuff, and
> you instruct it to do so).

Yes! I had exactly this happen to me while I was looking
at alternatives to the original posting.

Alf P. Steinbach

unread,
Jul 2, 2018, 1:41:23 PM7/2/18
to
On 01.07.2018 13:35, Manfred wrote:
>
> Moreover, the whole context object (With_faux_text or
> With_stream_detection) is hanging on the obscure '_' member - and one
> has to think about the guarantee that this actually ensures that the
> whole object is kept alive.

Well, let me shock you a little. :)

#include <iostream>
using namespace std;

struct Balalaika
{
char const _ = '-';
Balalaika() { cout << "Start\n"; }
~Balalaika() { cout << "End\n"; }
};

auto main()
-> int
{
char const& surely_a_nice_interview_question = Balalaika{}._;
cout << "The middle of main is that in Spain or this all in
vain?\n";
}

Cheers!,

- Alf

Manfred

unread,
Jul 2, 2018, 2:43:10 PM7/2/18
to
I know, which requires:
a) n4659 11.6.3 sub(5.2).
b) n4659 15.2 sub6, with its good list of exceptions.

>
> Cheers!,
>
> - Alf

Alf P. Steinbach

unread,
Jul 3, 2018, 8:30:00 AM7/3/18
to
On 01.07.2018 01:51, Tim Rentsch wrote:
> "Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
>
>> I can't recall having seen this approach, so posting it here:
>>
>> [..code was here..]
>
> Interesting idea. Perhaps a little weird, but definitely
> interesting!
>
> Here is a somewhat different means of arranging the cleanup.
> Note the single constructor call for 'Later it' below:
>
> #include <iostream>
> #include <string>
> #include <functional>
> #include <sstream>
>
> using namespace std;
>
> using Fixer = function< void() >;
>
> class Later {
> Fixer f;
> public:
> Later( Fixer f0 ) : f( f0 ) {}
> ~Later(){ f(); }
> };

I think this is an interesting approach, very C++11-like in its use of
functions rather than classes.

This is commonly called a “scope guard” class, AFAIK first invented
roughly in the year 2000 by Petru Marginean. His ¹DDJ article,
co-authored with Andrei Alexandrescu (author of “Modern C++ Design”), is
a classic. But most of it addresses C++03 concerns, at the time C++98
concerns, that are not particularly relevant in C++11 and later.

For example, as you show, `std::function` + lambdas provides one very
easy C++11 way to implement a scope guard, and the `auto` mechanism plus
type of cleanup function as template parameter is a
not-complex-but-more-verbose way to implement it, with marginal
improvement of efficiency. Marginean's main contribution was a trick to
do this in C++03, using lifetime extension of a temporary bound to a
reference to const. They also used a neat scheme for generating unique
names for temporaries, which however failed in Visual C++ when a certain
option was used because then Visual C++ bungled up the result of
standard __LINE__, so that one had to use the Microsoft-specific
__COUNTER__...

One nice feature of the original scope guard class was that a scope
guard could be /dismissed/. Microsoft's minimal implementation for the
²C++ guidelines, called ³gsl::final_action, misses that sometimes
critical functionality. But then, as I see it, one should really have
two distinct scope guard classes, one dismissable and one not, so that
what can happen, the possible effects on the code, is more clear.

The simple C++11 implementations, like yours and Microsoft's, implicitly
make an important design decision opposite of Marginean and Alexendrescu.

Namely, how to deal with exceptions in the cleanup code. Their code
suppressed such exceptions via a `catch( ... )`. I remember discussing
that with Andrei in a short e-mail exchange, because it bit me, but I
didn't get a satisfactory answer as to their rationale or goals.


> int
> main( int argc, char *argv[] ){
> Fixer supersede_with( const char * ), use_stdin();

Style nitpick: hiding function declarations inside a function hinders
at-a-glance code comprehension.


> bool faux = argc > 1 && string( argv[1] ) == "--faux-text";

Ditto style nitpick: when I see a variable that's not `const` I start
looking for the place where it's modified, how it affects the code.


> cout << " With" << (faux ? "" : "out") << " other input:\n";
>
> Later it( faux ? supersede_with( "Your ad here!" ) : use_stdin() );
> cout << " (here we are running the app)\n";
> }
>
> Fixer
> supersede_with( const char *other_input ){
> cout << " 'supersede_with' starting - " << other_input << '\n';
> istringstream in{ basic_string<char>( other_input ) };
> streambuf *s{ cin.rdbuf() };
> cin.rdbuf( in.rdbuf() );
>
> return [=](){
> cin.rdbuf( s );
> cout << " 'supersede_with' ending - " << other_input << "\n";
> };
> }
>
> Fixer
> use_stdin(){
> cout << " (any tests before using regular input...)\n";
> return [](){
> cout << " (we have been using regular input)\n";
> };
> }
>
>
> Disclaimer: I am certainly not an expert in C++ lambdas. AFAICT
> the above code is okay, but I confess my efforts to decipher what
> the C++ standard says could not be called entirely successful.
> Running the program produced the expected output in both case. >
> (Also thanks due to Manfred, from whose program I cribbed a bit.)

Thanks for this approach, I didn't think of it – stuck in Old Ways™. :)

Sorry for responding so late.

First I deferred responding and just starred the posting because I knew
it would take more than a minute; then the Eternal September clc++ feed
malfed for a day or so (I reported it); then there was a ~night.


Cheers!,

- Alf



Notes:
¹ <url:
http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758>
² <url:
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md>
³ <url: https://github.com/Microsoft/GSL/blob/master/include/gsl/gsl_util>

Alf P. Steinbach

unread,
Jul 4, 2018, 4:11:15 AM7/4/18
to
On 01.07.2018 19:08, Paavo Helde wrote:
> On 1.07.2018 14:35, Manfred wrote:
>
>> That said, your recalling of the lock guard raises a different
>> consideration - when writing:
>>
>>    std::lock_guard<std::mutex> guard(g_pages_mutex);
>>
>> what I still find annoying is that you may forget to name the thing
>> (because you don't use the name afterwards), and the resulting statement:
>>
>>    std::lock_guard<std::mutex>(g_pages_mutex);
>>
>> will compile, but be ineffective - most annoyingly, /silently/
>> ineffective (unless you have a compiler that can catch this stuff, and
>> you instruct it to do so).
>
> This is one of the few occasions I have found macros useful. All my
> mutex locks go through macros which define a lock variable, e.g:
>
> SCOPED_LOCK(g_pages_mutex);

This seems to imply one such macro for each type where the RAII wrapper
idiom is used.

I wonder how you dealt with the name of that object, considering that
one might want to have two or more such objects in the same scope?


> This has several benefits IMO:
>
> 1. Avoids stumbling upon the most vexing parse which you brought up.
>
> 2. Can easily add, remove and modify run-time checks (potentially in
> debug mode compilation only) for trouble-shooting the multi-threading
> issues: checking against locking a non-recursive mutex recursively,
> checking against locking mutexes in inconsistent order, i.e. potential
> deadlocks, monitoring the duration of locks etc. There are diagnostic
> tools to achieve similar goals without source code modifications, but in
> my experience they have appeared either non-effective or too slow to
> accomplish anything.
>
> 3. Can portably add filename and line number in the diagnostics for
> above checks.
>
> 4. The lock variable name is hidden which is fine as it does not matter
> most of the time (it's needed only when one wants to wait on the mutex).

A more general macro like exemplified below keeps benefits 1 and 4, but
if used directly appears to lose benefits 2 and 3.

Direct usage would go like

using Lock = std::lock_guard<std::mutex>;

//...
BLAHBLAH_WITH( Lock{ g_pages_mutex } )
{
// code
}

where a C++17 definition of BLAHBLAH_LOCK can go like this:

#define BLAHBLAH_WITH( ... ) \
if( const auto& _ = __VA_ARGS__; !!&_ ) // The !!&_ avoids
warning about unused.

Here the variable's name isn't so much of a problem since the macro
introduces a nested scope.

* * *

I've found that the main problem with defining such a macro is to make
the usage syntax natural.

With earlier versions of C++ one would have to use a definition where
the usage would look like

BLAHBLAH_WITH( Lock,( g_pages_mutex ) )

or

BLAHBLAH_WITH( Lock, g_pages_mutex )

in order to separate the constructor arguments from the type.

The latter usage syntax is perhaps more conventional, e.g. it's used by
`std::thread` constructor.

* * *

A lesser problem is the all uppercase shouting and noisy prefixes like
BLAHBLAH_. The fearless programmer can use `$` as a macro name prefix.
It's non-standard but all compilers seem to support it.

Potential problem: when Herb Sutter tried that for his library code
(just after I'd tried that for my experimental library code), with a
large number of testers, some complained that their company used some
preprocessing scheme where the `$` was significant. So as I understand
it he ditched that scheme. But it's there, for the fearless. :)

E.g. after seeing your posting I defined


----------------------------------------------------------------------
#pragma once

// For example
// $with( Lock{ mutex } ) access_resource();

#if not defined( CPPX_NO_DOLLAR_NAMES )
# define $with CPPX_WITH
#endif

#define CPPX_WITH( ... ) \
if( const auto& _ = __VA_ARGS__; !!&_ ) // The !!&_ avoids
warning about unused.
-----------------------------------------------------------------------


Cheers!,

- Alf

Paavo Helde

unread,
Jul 4, 2018, 5:26:38 AM7/4/18
to
On 4.07.2018 11:11, Alf P. Steinbach wrote:
> On 01.07.2018 19:08, Paavo Helde wrote:
>> On 1.07.2018 14:35, Manfred wrote:
>>
>>> That said, your recalling of the lock guard raises a different
>>> consideration - when writing:
>>>
>>> std::lock_guard<std::mutex> guard(g_pages_mutex);
>>>
>>> what I still find annoying is that you may forget to name the thing
>>> (because you don't use the name afterwards), and the resulting
>>> statement:
>>>
>>> std::lock_guard<std::mutex>(g_pages_mutex);
>>>
>>> will compile, but be ineffective - most annoyingly, /silently/
>>> ineffective (unless you have a compiler that can catch this stuff, and
>>> you instruct it to do so).
>>
>> This is one of the few occasions I have found macros useful. All my
>> mutex locks go through macros which define a lock variable, e.g:
>>
>> SCOPED_LOCK(g_pages_mutex);
>
> This seems to imply one such macro for each type where the RAII wrapper
> idiom is used.

Yes, I have actually 2 such macros, one for the standard mutex and one
for the recursive mutex. I have not needed them for anything else as
non-multi-threaded problems are much easier and do not typically require
such "instrumented debugging". Also, the effects of declaring a function
instead of the intended RAII variable are typically much more obvious
than in case of mutex locks.

>
> I wonder how you dealt with the name of that object, considering that
> one might want to have two or more such objects in the same scope?

Easy, just define an extra inner {} scope. For mutex locks one often
needs extra scopes anyway to reduce the lock duration and to make the
code more readable.


Tim Rentsch

unread,
Jul 13, 2018, 8:56:35 AM7/13/18
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:

> On 01.07.2018 01:51, Tim Rentsch wrote:
>
>> "Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
>>
>>> I can't recall having seen this approach, so posting it here:
>>>
>>> [..code was here..]
>>
>> Interesting idea. Perhaps a little weird, but definitely
>> interesting!
>>
>> Here is a somewhat different means of arranging the cleanup.
>> Note the single constructor call for 'Later it' below:
>>
>> #include <iostream>
>> #include <string>
>> #include <functional>
>> #include <sstream>
>>
>> using namespace std;
>>
>> using Fixer = function< void() >;
>>
>> class Later {
>> Fixer f;
>> public:
>> Later( Fixer f0 ) : f( f0 ) {}
>> ~Later(){ f(); }
>> };
>
> I think this is an interesting approach, very C++11-like in its use of
> functions rather than classes.
>
> This is commonly called a ?scope guard? class, AFAIK first invented
> roughly in the year 2000 by Petru Marginean. His DDJ article,
> co-authored with Andrei Alexandrescu (author of ?Modern C++ Design?),
> is a classic. But most of it addresses C++03 concerns, at the time
> C++98 concerns, that are not particularly relevant in C++11 and later.

To me the key idea is using a closure to hold a deferred action.
Tying the closure to an object destructor is just a detail of
this application.

> For example, as you show, `std::function` + lambdas provides one
> very easy C++11 way to implement a scope guard, and the `auto`
> mechanism plus type of cleanup function as template parameter is a
> not-complex-but-more-verbose way to implement it, with marginal
> improvement of efficiency. Marginean's main contribution was a
> trick to do this in C++03, using lifetime extension of a temporary
> bound to a reference to const. [...]

I have the sense that the cleverness is needed to get around a
fundamental property of object construction being tied to
declarations, which have a fixed type. That makes me wonder
whether this technique might be not the right tool to use. If I
were faced with a similar problem in the future I would probably
look for a different approach.

> One nice feature of the original scope guard class was that a
> scope guard could be /dismissed/. [...] But then, as I see it,
> one should really have two distinct scope guard classes, one
> dismissable and one not, so that what can happen, the possible
> effects on the code, is more clear.

Spot on. I might even say it more strongly, that the two are
totally different animals, and using the same name for both
(modulo the "dismissable" adjective) is an accident waiting
to happen.

> The simple C++11 implementations, like yours and Microsoft's,
> implicitly make an important design decision opposite of
> Marginean and Alexendrescu.

Presumably they wanted to provide flexibility. But I agree
it is important to distinguish the two different cases.

> Namely, how to deal with exceptions in the cleanup code. Their
> code suppressed such exceptions via a `catch( ... )`. I remember
> discussing that with Andrei in a short e-mail exchange, because
> it bit me, but I didn't get a satisfactory answer as to their
> rationale or goals.

That sounds like it might be interesting but as details
were left out I will drop it and move on. :)

>> int
>> main( int argc, char *argv[] ){
>> Fixer supersede_with( const char * ), use_stdin();
>
> Style nitpick: hiding function declarations inside a function
> hinders at-a-glance code comprehension.

I deliberately put the declarations inside the definition of
main(), both to limit visibility and to indicate that they are in
effect "private" functions. It would have been better though to
put in an explicit "extern".

>> bool faux = argc > 1 && string( argv[1] ) == "--faux-text";
>
> Ditto style nitpick: when I see a variable that's not `const` I
> start looking for the place where it's modified, how it affects
> the code.

Different people have different ideas about when using 'const' is
important. I think people in the C++ community tend to be more
prone to using 'const' reflexively. I don't do that. I do use
'const' in places where I think it adds more than it costs; in
very short functions, like this one, it seems like more of a
distraction than a help, which gives a net negative. I realize
other people have different views or put different weights on the
various factors. YMMV.

>> cout << " With" << (faux ? "" : "out") << " other input:\n";
>>
>> Later it( faux ? supersede_with( "Your ad here!" ) : use_stdin() );
>> cout << " (here we are running the app)\n";
>> }
>>
>> Fixer
>> supersede_with( const char *other_input ){
>> cout << " 'supersede_with' starting - " << other_input << '\n';
>> istringstream in{ basic_string<char>( other_input ) };
>> streambuf *s{ cin.rdbuf() };
>> cin.rdbuf( in.rdbuf() );
>>
>> return [=](){
>> cin.rdbuf( s );
>> cout << " 'supersede_with' ending - " << other_input << "\n";
>> };
>> }
>>
>> Fixer
>> use_stdin(){
>> cout << " (any tests before using regular input...)\n";
>> return [](){
>> cout << " (we have been using regular input)\n";
>> };
>> }
>>
>>
>> Disclaimer: I am certainly not an expert in C++ lambdas. AFAICT
>> the above code is okay, but I confess my efforts to decipher what
>> the C++ standard says could not be called entirely successful.
>> Running the program produced the expected output in both case. >
>> (Also thanks due to Manfred, from whose program I cribbed a bit.)
>
> Thanks for this approach, I didn't think of it ? stuck in Old Ways?. :)

Thank you very much. Is there some reason you were expecting it
to be tied to "Old Ways"?

> Sorry for responding so late. [...]

No worries. I can't afford to always (or even often) give
responses quickly, 'cause I got other things in need of attention
that must get priority. So I can't very well let some "late"
responses from other people bother me. :)
0 new messages