Elegant way to push all declarations with a particular pattern-match into vector?

Viewed 117

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_
2 Answers

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

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.

Related