(Note: GitHub project FunctorFun explores this topic and has simple, working code.)
Boost and Program Options
Many C++ developers are familiar with Boost — a free, huge, cross-platform, and high-quality collection of libraries. One of these libraries, program_options, has some of the most intriguing syntactic sugar I have ever seen.
The library is used for parsing configuration files and command-lines options. One of the components of the library is the options_description, which is used to declare the symbolic name, the target program variable associated with the option, and help-text. Below is an example of using options_description:
options_description desc; desc.add_options() ("help", "produce help message") ("compression", value<string>(), "compression level") ("verbose", value<string>()->implicit_value("0"), "verbosity level") ("email", value<string>()->multitoken(), "email to send to") ;
Certainly this is an elegant way to handle a lot of messy detail, but I could not get over being bothered by it. How does it even compile? The above code is fed directly to the C++ compiler: no preprocessor converts this into more familiar looking C++.
I was willing to accept that it works, but I could not get comfortable with the fact that I could not even posit an order of operations that would make sense.
The Functors Did It
Mercifully, a Google search turned up an article from the Stack Overflow forum that explains the magic. In short, it’s functors.
The term ‘functor’ is from logic and math. It is a mapping or predicate applied to a domain element and yielding a range element. You might think of it as a reified process or function.
In C++, we use a more prosaic definition: a functor is a class that has its function call operator (i.e., the parens) defined.
In the above coding snippet, the return type of method add_options() is options_description_easy_init. And this class declares several function call operators with various signatures, each of which returns a reference to its options_description_easy_init instance.
In the example code snippet:
- Function add_options() instantiates and returns an instance of options_description_easy_init.
- The instance executes its function operator whose signature is two strings (n.b., it is passed arguments “help” and “produce help message”). The function operator returns a reference to the original instance.
- The instance executes its function operator whose signature is (string, value<>, string), and returns a reference to the same options_description_easy_init instance.
- The instance again executes the function operator with signature (string, value<>, string). It returns a reference to the original instance, which is ignored.
This is an example of the chaining idiom: an object’s member function returning a reference to the object, which is used to execute additional member functions. Perhaps the most familiar example of C++ chaining is the the output operator, <<. In Java, the StringBuilder class demonstrates chaining.
This use of functors is fascinating and fun, but I think it belongs in the user-facing API of a library, and only when the library is a black box devoid of business logic. It seems to me to be too tricky for code you need to maintain, and too hard to explain new hires.
On the other hand, the use of functors and chaining in the program_options library is brilliant. It greatly reduces tedium and code clutter, and facilitates readability and maintainability. And it sparks interest in arcane C++ syntax.
(As noted in the first line of this post, GitHub project FunctorFun has simple, working code that illustrates chaining with functors.)