C++ terseness challenge!
-
As an exercise, I set myself a goal: given a
QButtonGroup
ofQCheckBox
es --- non-exclusive, so multiple ones can be checked --- return a list/vector of which buttons/checkboxes are checked.I'm finding my way around some the
std::
stuff. I'd like the "tersest"/shortest/neatest solution. This is for C++ less than C++20 (there's a reason for that, i.e. nostd::ranges
!).So here is what I have come up with:
auto buttons = ui->checkButtonGroup->buttons(); std::vector<QAbstractButton *> checkedButtons; std::copy_if(buttons.begin(), buttons.end(), std::back_inserter(checkedButtons), [](const QAbstractButton *btn) { return btn->isChecked(); }); return checkedButtons;
Now, I am not a great fan of Python, but I have to admit here my (untested) solution would be:
return [btn for btn in ui.checkButtonGroup.buttons() if btn.isChecked()]
You have to admit, it's an awful lot shorter/simpler!
So.... can any C++ guru offer anything "better" for C++ < 20?! :)
-
auto buttons = ui->checkButtonGroup->buttons(); buttons.erase(std::remove_if(buttons.begin(), buttons.end(), [](const QAbstractButton *btn) { return btn->isChecked(); }), buttons.end()); return buttons;
-
auto buttons = ui->checkButtonGroup->buttons(); buttons.erase(std::remove_if(buttons.begin(), buttons.end(), [](const QAbstractButton *btn) { return btn->isChecked(); }), buttons.end()); return buttons;
@Christian-Ehrlicher
This is interesting, but I regard it as "cheating" ;-)You are altering the
QList<>
returned byui->checkButtonGroup->buttons()
. In practice I might well want the function to take aconst QList<> &
parameter of the buttons, instead of passing in theQButtonGroup *
. It's too late now to alter, perhaps instead ofauto buttons = ui->checkButtonGroup->buttons();
I should have demanded you start with, say,const auto buttons ...
, or equivalent. That stops yourbuttons.erase()
/std::remove_if()
!Nonetheless, your approach shows that it can be done shorter if we allow modification. I will buy you a warm English beer instead of a cold German beer for this :)
I imagine this is as good as it gets from C++... I wish they had a Python equivalent!
-
As an exercise, I set myself a goal: given a
QButtonGroup
ofQCheckBox
es --- non-exclusive, so multiple ones can be checked --- return a list/vector of which buttons/checkboxes are checked.I'm finding my way around some the
std::
stuff. I'd like the "tersest"/shortest/neatest solution. This is for C++ less than C++20 (there's a reason for that, i.e. nostd::ranges
!).So here is what I have come up with:
auto buttons = ui->checkButtonGroup->buttons(); std::vector<QAbstractButton *> checkedButtons; std::copy_if(buttons.begin(), buttons.end(), std::back_inserter(checkedButtons), [](const QAbstractButton *btn) { return btn->isChecked(); }); return checkedButtons;
Now, I am not a great fan of Python, but I have to admit here my (untested) solution would be:
return [btn for btn in ui.checkButtonGroup.buttons() if btn.isChecked()]
You have to admit, it's an awful lot shorter/simpler!
So.... can any C++ guru offer anything "better" for C++ < 20?! :)
@JonB said in C++ terseness challenge!:
C++ terseness ....
stl
...Qt is the thing that allows C++ to be terse and readable. "stl" and "terse" don't fit in the same sentence ;-P
I imagine this is as good as it gets from C++... I wish they had a Python equivalent!
What's the reason for excluding C++20 and
std::ranges
?Nonetheless, your approach shows that it can be done shorter if we allow modification.
No modification required in Qt:
return QtConcurrent::blockingFiltered( ui->checkButtonGroup->buttons(), [](const QAbstractButton *btn) { return btn->isChecked(); });
EDIT: Warning: This code technically violates the API rules, as widget methods are only allowed to be called from the GUI thread. However,
isChecked()
is a const lookup of a bitfield andblockingFiltered()
blocks the GUI thread so it's harmless. -
@JonB said in C++ terseness challenge!:
C++ terseness ....
stl
...Qt is the thing that allows C++ to be terse and readable. "stl" and "terse" don't fit in the same sentence ;-P
I imagine this is as good as it gets from C++... I wish they had a Python equivalent!
What's the reason for excluding C++20 and
std::ranges
?Nonetheless, your approach shows that it can be done shorter if we allow modification.
No modification required in Qt:
return QtConcurrent::blockingFiltered( ui->checkButtonGroup->buttons(), [](const QAbstractButton *btn) { return btn->isChecked(); });
EDIT: Warning: This code technically violates the API rules, as widget methods are only allowed to be called from the GUI thread. However,
isChecked()
is a const lookup of a bitfield andblockingFiltered()
blocks the GUI thread so it's harmless.@JKSH said in C++ terseness challenge!:
What's the reason for excluding C++20 and std::ranges?
Too easy? :) And too new!
QtConcurrent::blockingFiltered(...)
Oohh! That is indeed the sort of thing I was looking for, but did not know existed! (Never used
QtConcurrent
.) So Qt does have the sort of "filtering" which I looked for instd
but did not find?!I don't mean to be picky now, but: I don't want/need anything to be done concurrently. Of course I can see why this is a useful
QtConcurrent
feature. But isn't there any Qt non-concurrent equivalent to this, because it's useful (for my question) to have this available for bog-standard serial programming? -
Hi,
blockingFiltered will block until everything has been processed.
-
Hi,
blockingFiltered will block until everything has been processed.
@SGaist
Yes, I do realize that! And I do realize that havingblockingFiltered
inQtConcurrent
means that it can, in principle, take advantage of parallelism. I just would be happy with a plain, serial implementation which did not require concurrent/live in the concurrent namespace/library. So I wondered whether Qt had a non-concurrent method for filtering container-lists, but it seems not? -
@JKSH said in C++ terseness challenge!:
What's the reason for excluding C++20 and std::ranges?
Too easy? :) And too new!
QtConcurrent::blockingFiltered(...)
Oohh! That is indeed the sort of thing I was looking for, but did not know existed! (Never used
QtConcurrent
.) So Qt does have the sort of "filtering" which I looked for instd
but did not find?!I don't mean to be picky now, but: I don't want/need anything to be done concurrently. Of course I can see why this is a useful
QtConcurrent
feature. But isn't there any Qt non-concurrent equivalent to this, because it's useful (for my question) to have this available for bog-standard serial programming?@JonB said in C++ terseness challenge!:
I don't mean to be picky now, but: I don't want/need anything to be done concurrently.... isn't there any Qt non-concurrent equivalent to this, because it's useful (for my question) to have this available for bog-standard serial programming?
Not that I know of, but I believe you can force your vector/list elements to be processed one at a time by calling
QThreadPool::globalInstance()->setMaxThreadCount(1);
beforehand.P.S. You're not the only one who wants Qt Concurrent to not run concurrently
-
@JonB said in C++ terseness challenge!:
I don't mean to be picky now, but: I don't want/need anything to be done concurrently.... isn't there any Qt non-concurrent equivalent to this, because it's useful (for my question) to have this available for bog-standard serial programming?
Not that I know of, but I believe you can force your vector/list elements to be processed one at a time by calling
QThreadPool::globalInstance()->setMaxThreadCount(1);
beforehand.P.S. You're not the only one who wants Qt Concurrent to not run concurrently
@JKSH
I saw that one earlier on... :) No, I do not mean I do not wantQtConcurrent
to run concurrently! Like I said, I totally see why this is a useful method in concurrent. All I meant here, purely out of interest, is whether/why Qt does not bother to supply this convenience function against containers for the non-concurrent case. (Just to save me having to write it myself like in my code in first post in this thread.) Like the Python. That's all. -
@JKSH
I saw that one earlier on... :) No, I do not mean I do not wantQtConcurrent
to run concurrently! Like I said, I totally see why this is a useful method in concurrent. All I meant here, purely out of interest, is whether/why Qt does not bother to supply this convenience function against containers for the non-concurrent case. (Just to save me having to write it myself like in my code in first post in this thread.) Like the Python. That's all.@JonB said in C++ terseness challenge!:
All I meant here, purely out of interest, is whether/why Qt does not bother to supply this convenience function against containers for the non-concurrent case.
I suspect there's no strong reason; it's just that nobody has wanted it badly enough to put it into Qt.
Now, given that
QtAlgorithms
is largely deprecated and C++20 has landed, I think it's highly unlikely that Qt will ever get a non-concurrent filter function.No, I do not mean I do not want
QtConcurrent
to run concurrently!My bad, I shouldn't jest like that :-D
Seriously though, there are legitimate reasons to avoid concurrent processing. For example, your program can crash if you use QtConcurrent to run a non-const function on the elements of a
QList<QWidget*>
, because widgets are not thread-safe. -
I would say that you already have one of the best solutions. I see basically 2 options: use declarative syntax like you did or use imperative syntax. By itself the language part of C++ is imperative programming, only the STL is more declarative. You want to copy elements under certain circumstances?
std::copy_if
exactly states this intent. I have learned that since C++11 you should usestd::begin
/end
instead (or their constant counterparts, respectively):std::copy_if(std::cbegin(buttons), std::cend(buttons), std::back_inserter(checkedButtons), [](const QAbstractButton *btn) { return btn->isChecked(); });
This would make it more general that in certain cases you could also use an array instead of higher level data structures.
There is one more shortcut to your approach. The predicate of
copy_if
can be used directly:std::copy_if(std::cbegin(buttons), std::cend(buttons), std::back_inserter(checkedButtons), std::mem_fn(&QAbstractButton::isChecked));
isChecked
is already a predicate. The only problem is that it is a member function, hence the use ofmem_fn
to wrap it.Now to the second solution: imperative programming:
for(auto button : buttons) if(button->isChecked()) checkButtons.push_back(button);
To me this looks like a valid solution which at least is less to type. Since the loop is short I would judge this to be readable. This is down to taste if you prefer imperative or declarative programming.
Finally, even without using C++20 you could still use ranges as the proposal is based on an older implementation of this library: https://github.com/ericniebler/range-v3 . Would this "cheat" be allowed?
-
I would say that you already have one of the best solutions. I see basically 2 options: use declarative syntax like you did or use imperative syntax. By itself the language part of C++ is imperative programming, only the STL is more declarative. You want to copy elements under certain circumstances?
std::copy_if
exactly states this intent. I have learned that since C++11 you should usestd::begin
/end
instead (or their constant counterparts, respectively):std::copy_if(std::cbegin(buttons), std::cend(buttons), std::back_inserter(checkedButtons), [](const QAbstractButton *btn) { return btn->isChecked(); });
This would make it more general that in certain cases you could also use an array instead of higher level data structures.
There is one more shortcut to your approach. The predicate of
copy_if
can be used directly:std::copy_if(std::cbegin(buttons), std::cend(buttons), std::back_inserter(checkedButtons), std::mem_fn(&QAbstractButton::isChecked));
isChecked
is already a predicate. The only problem is that it is a member function, hence the use ofmem_fn
to wrap it.Now to the second solution: imperative programming:
for(auto button : buttons) if(button->isChecked()) checkButtons.push_back(button);
To me this looks like a valid solution which at least is less to type. Since the loop is short I would judge this to be readable. This is down to taste if you prefer imperative or declarative programming.
Finally, even without using C++20 you could still use ranges as the proposal is based on an older implementation of this library: https://github.com/ericniebler/range-v3 . Would this "cheat" be allowed?
@SimonSchroeder
Thank you for your further tips.One of my "goals" is (often) to get things down to no more than one line of typing/screen estate. (I don't think you can overestimate how useful to the programming experience it is to have as much code as possible visible in one vertical page when editing!). So your
std::copy_if(std::cbegin(buttons), std::cend(buttons), std::back_inserter(checkedButtons), std::mem_fn(&QAbstractButton::isChecked));
is pleasing :)
The "irritation" for me of the standard
for(auto button : buttons)
loop comes when debugging the app. As you use "step over" in the calling code quickly you end up in a loop you have no interest in. Yes, you can then do a "run to" (if debugger has it), or place a breakpoint afterward and go "continue", but it's a pain. One day I'd like to write code which does not have a singlefor
orwhile
in any method (only factored away into sub-functions) so as to avoid this! Yes, I could write my own function for those 3 lines, but that's a hassle, and yet more lines of my own code to write.Like I said, I'm not a great fan from my Python experience, but the
[ something for something in somewhat if somesuch ]
, while looking a bit ugly, plus the variety ofall()
/any()
/filter()
built-ins, does make it easy to have one-liners to do what you want and step over in one go. -
@SimonSchroeder
Thank you for your further tips.One of my "goals" is (often) to get things down to no more than one line of typing/screen estate. (I don't think you can overestimate how useful to the programming experience it is to have as much code as possible visible in one vertical page when editing!). So your
std::copy_if(std::cbegin(buttons), std::cend(buttons), std::back_inserter(checkedButtons), std::mem_fn(&QAbstractButton::isChecked));
is pleasing :)
The "irritation" for me of the standard
for(auto button : buttons)
loop comes when debugging the app. As you use "step over" in the calling code quickly you end up in a loop you have no interest in. Yes, you can then do a "run to" (if debugger has it), or place a breakpoint afterward and go "continue", but it's a pain. One day I'd like to write code which does not have a singlefor
orwhile
in any method (only factored away into sub-functions) so as to avoid this! Yes, I could write my own function for those 3 lines, but that's a hassle, and yet more lines of my own code to write.Like I said, I'm not a great fan from my Python experience, but the
[ something for something in somewhat if somesuch ]
, while looking a bit ugly, plus the variety ofall()
/any()
/filter()
built-ins, does make it easy to have one-liners to do what you want and step over in one go.@JonB Interesting that the first line of your previous made me think exactly of debugging issues. I spend a lot of time right now in ddd/gdb doing network protocol design so I need a lot of single line auto var operations as breakpoints to intelligently inspect state. It takes a while to customize but ddd is still the only gui gdb front end for me. I spent hours with resedit identifing X11 resources to override and trying to get an init gdb command file to batch execute my target app to the point where I expect the error to occur and automatically show the offending blobs. off course it also has to spred the separate displayes across my monitors in a fashion that eases my OCD.
I also cannot stand coding style rules that spread an if construct over two scrolled pages. Like you, I want to see a completely expressed idea in a concise manner on as few lines as possible.
-
@SimonSchroeder
Thank you for your further tips.One of my "goals" is (often) to get things down to no more than one line of typing/screen estate. (I don't think you can overestimate how useful to the programming experience it is to have as much code as possible visible in one vertical page when editing!). So your
std::copy_if(std::cbegin(buttons), std::cend(buttons), std::back_inserter(checkedButtons), std::mem_fn(&QAbstractButton::isChecked));
is pleasing :)
The "irritation" for me of the standard
for(auto button : buttons)
loop comes when debugging the app. As you use "step over" in the calling code quickly you end up in a loop you have no interest in. Yes, you can then do a "run to" (if debugger has it), or place a breakpoint afterward and go "continue", but it's a pain. One day I'd like to write code which does not have a singlefor
orwhile
in any method (only factored away into sub-functions) so as to avoid this! Yes, I could write my own function for those 3 lines, but that's a hassle, and yet more lines of my own code to write.Like I said, I'm not a great fan from my Python experience, but the
[ something for something in somewhat if somesuch ]
, while looking a bit ugly, plus the variety ofall()
/any()
/filter()
built-ins, does make it easy to have one-liners to do what you want and step over in one go.@JonB said in C++ terseness challenge!:
Yes, I could write my own function for those 3 lines
I once heard from a colleague that they sold their code to a different company. They had a rule that all functions were allowed to have at most 3 lines of code. So, they started intensive refactoring to meat that goal. In your case you did plan for a separate function in your small example. This would be a 6-line function which I would say is okay. But as I said, this is down to preference.
I thought that there were proposals to extend C++ so that the algorithm functions can take a container directly instead of its iterators. However, I couldn't find anything on that right away. I would immediately switch to this approach if it were slightly less verbose. Does anyone have information if we can now write this?
std::copy_if(buttons, std::back_inserter(checkedButtons), std::mem_fn(&QAbstractButton::isChecked));
I thought this was already introduced with
std::span
, but couldn't find it on cppreference.com.