3

I'm refactoring some code and curious if there is a modern C++ feature that allows me to dynamically push all matching declarations into a vector without having to manually type out every parameter name.

For example;

I have the following declarations in my header (instantiated elsewhere);

Fl_Button * button_file_browse;
Fl_Button * button_parse_txt;
Fl_Button * button_parse_xer;
Fl_Button * button_file_browse;
Fl_Button * button_perform_sentiment;
Fl_Button * button_txt_folder_browse;

Then elsewhere I push these all into a vector one-by-one; it's a little painful with tons of widgets. Especially if I add a new Fl_Button I need to go and add it to this piece of code as well.

std::vector<Fl_Button *> vector_fl_button;
vector_fl_button.push_back(button_file_browse);
vector_fl_button.push_back(button_parse_txt);
vector_fl_button.push_back(button_parse_xer);
vector_fl_button.push_back(button_perform_sentiment);
vector_fl_button.push_back(button_txt_folder_browse);

Is there a beautiful C++ feature that allows me to type something as elegant as below?

std::vector<Fl_Button *> vector_fl_button;
vector_fl_button.push_back(button_*); // Pushes all pointers starting with button_
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
jparanich
  • 8,372
  • 4
  • 26
  • 34
  • 3
    C++ doesn't have refelection, so you can't use the language itself to build the vector. Do you really need to have the variables? Can't you just have the vector, or better yet a struct/class that has all of the variables you want in it? – NathanOliver May 06 '21 at 16:19
  • If I'm understanding right, I need all the variables for specific callbacks tied to each button. I'm pushing all the pointers to Fl_Button into a vector so I can then walk the vector and adjust color schema settings on all the buttons in one swoop. – jparanich May 06 '21 at 16:21
  • 3
    Whenever I have some tedious and repetitive editing to do, I first consider editor macros (emacs F3, F4 or Visual Studio CTRL-SHIFT-R, CTRL-SHIFT-P). If that is not an option I write myself a few lines of Common Lisp and generate the c++ source code, then copy and paste it where I want it. – BitTickler May 06 '21 at 16:21
  • 1
    How about an initializer list like `std::vector vector_fl_button = { button_file_browse, button_parse_txt, button_parse_xer, button_file_browse2, button_perform_sentiment, button_txt_folder_browse };`? [Demo on coliru](http://coliru.stacked-crooked.com/a/e4e07ae5f68cde67) – Scheff's Cat May 06 '21 at 16:41
  • That is certainly cleaner; but still requires elbow grease and upkeep when new Fl_Buttons are added down road. I suppose like Nathan said, reflection isn't part of C++ despite all the numerous other additions over the years. This seems like it would be a very useful feature. – jparanich May 06 '21 at 16:54
  • Are you using Qt? – Ted Lyngmo May 06 '21 at 17:23
  • 1
    Ted, I'm using FLTK – jparanich May 06 '21 at 20:05

2 Answers2

2

Just to showcase, how simple code generation can be if you have a powerful scripting language at hand. I usually use Common Lisp because I use emacs anyway and so it is all done "in-house". And I usually program in Lisp and only sometimes I regress to my old go-to language C++...

Especially for maintenance, it might pay off and reduce "oversight errors". Since I use cl-ppcre which is the Lisp regular expression engine, you could even consider to exploit your habits of doing things in your favor.

For example, if you always have 1 header file with your button declarations in your single class in that header, you could just use the header file instead of copy and pasting the button declarations into the lisp code...

The few lines of lisp below just show the basics. And if Lisp is not your thing, I guess you can do the same in APL or Haskell or Perl or Julia or even in Python.

(defparameter *my-buttons*
  "Fl_Button * button_file_browse;
Fl_Button * button_parse_txt;
Fl_Button * button_parse_xer;
Fl_Button * button_file_browse;
Fl_Button * button_perform_sentiment;
Fl_Button * button_txt_folder_browse;")

(defparameter *button-decl-pattern*
  (ppcre:create-scanner "\\s*Fl_Button\\s*\\*\\s*(button_\\w+);"))

(defun button-names (&optional (decls *my-buttons*))
  (with-input-from-string (stream decls)
    (loop for line = (read-line stream nil)
      while line
      collecting
      (aref
       (nth-value 1 (ppcre:scan-to-strings
             *button-decl-pattern*
             line))
       0))))

(defun vectorize-buttons (buttons)
  (with-output-to-string (stream)
    (format stream "std::vector<Fl_Button *> vector_fl_button;~%")
    (loop for button in buttons do
      (format stream
          "vector_fl_button.push_back(~A);~%"
          button))))

CL-USER> (vectorize-buttons (button-names))
"std::vector<Fl_Button *> vector_fl_button; vector_fl_button.push_back(button_file_browse); vector_fl_button.push_back(button_parse_txt); vector_fl_button.push_back(button_parse_xer); vector_fl_button.push_back(button_file_browse); vector_fl_button.push_back(button_perform_sentiment); vector_fl_button.push_back(button_txt_folder_browse); "

BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • Thanks for taking the time for such a detailed response; it did trigger in my mind that I could probably also grep out the relevant strings to get an easy copy/paste list. – jparanich May 07 '21 at 16:34
1

Is there a beautiful C++ feature that allows me to type something as elegant as below?

std::vector<Fl_Button *> vector_fl_button;
vector_fl_button.push_back(button_*); 

No, not built-in. However, FLTK makes it possible to build the vector dynamically. The name of the objects in the C++ code is however lost in compilation so you'll need to find some other criteria.

Fortunately, Fl_Widgets, like Fl_Button have a user_data() function that can return a void* or long (really a void* that you need to cast to a long). So, if you don't want a vector of all Fl_Buttons, you can set user_data when designing the windows. Here I've used fluid to set the user_data value on a button that I like to be included in the vector:

enter image description here

Here's a function to find all widgets of a certain type placed inside another widget (like a Fl_Window) and to apply a unary predicate on the found widgets. If the predicate returns true, it stores the pointer.

widget_funcs.hpp

#ifndef WIDGET_FUNCS_HPP
#define WIDGET_FUNCS_HPP

#include <FL/Fl_Group.H>

#include <algorithm>
#include <iterator>
#include <vector>

template <class T, class UnaryPredicate>
auto find_widgets_of_type(Fl_Widget* w, UnaryPredicate pred) {
    std::vector<T*> rv;

    // check if "w" is a container class (essentially a dynamic_cast):
    Fl_Group* g = w->as_group();

    if(g) {
        // Copy all the pointers that can be dynamically casted to T*
        // and that fulfills the conditions in the predicate function.
        std::for_each(g->array(), std::next(g->array(), g->children()),
                      [&rv,&pred](Fl_Widget* child) {
                          auto isT = dynamic_cast<T*>(child);
                          if(isT && pred(isT)) rv.push_back(isT);
                      });
    }
    return rv;
}

template <class T>  // use this overload if you want all Fl_Buttons
auto find_widgets_of_type(Fl_Widget* w) {
    return find_widgets_of_type<T>(w, [](const T*){ return true; });
}

#endif

You can then call the above function with the widget you'd like to get Fl_Buttons from as an argument:

#include "widget_funcs.hpp"

class SomeClass {
public:
    SomeClass(Fl_Widget* window) :
        vector_fl_button(
            find_widgets_of_type<Fl_Button>(window, [](Fl_Button* b) {
                return reinterpret_cast<long>(b->user_data()) == 1;
            }))
    {}
    // ...

private:
    std::vector<Fl_Button*> vector_fl_button;
};

If you need to find buttons in grandchildren too, you could just make the function recursive.

If you already use user_data for something else, you can replace the predicate in the example above with some other property that is common for all the buttons you'd like to have in the vector.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Wow Ted, thanks! This is the best I'm going to get, and I do indeed use fluid, so this is perfect. Thanks a lot. – jparanich May 07 '21 at 16:32
  • @jparanich Glad it worked :) You're welcome! I tried to put some of the code inside the generated user interface classes in `fluid` but `fluid` got really confused by lambdas so I stopped that attempt :) – Ted Lyngmo May 07 '21 at 16:47