Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

C++ terseness challenge!



  • As an exercise, I set myself a goal: given a QButtonGroup of QCheckBoxes --- 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. no std::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?! :)


  • Lifetime Qt Champion

    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 by ui->checkButtonGroup->buttons(). In practice I might well want the function to take a const QList<> & parameter of the buttons, instead of passing in the QButtonGroup *. It's too late now to alter, perhaps instead of auto buttons = ui->checkButtonGroup->buttons(); I should have demanded you start with, say, const auto buttons ..., or equivalent. That stops your buttons.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!


  • Moderators

    @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 and blockingFiltered() 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 in std 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?


  • Lifetime Qt Champion

    Hi,

    blockingFiltered will block until everything has been processed.



  • @SGaist
    Yes, I do realize that! And I do realize that having blockingFiltered in QtConcurrent 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?


  • Moderators

    @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 want QtConcurrent 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.


  • Moderators

    @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 use std::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 of mem_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 single for or while 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 of all()/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.



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


Log in to reply