Wednesday, January 2, 2013

C++ named argument builder pattern

I was looking for a C++ builder pattern for passing named arguments to functions, but the example I found on Wikipedia is horrendous. Someone on Stack Overflow mentioned fluent interface which is much better, but the example given in Wikipedia is also horrendous because it involved two classes, one the actual object and the other one is the wrapper employing the fluent interface. Another answer on Stack Overflow is much better, but the example also involved two classes, one the real class and the other the builder. For the purpose of passing named arguments to functions, what I have in mind is more light weight.
#include <iostream>
#include <string>

struct RepeatArgs {
  RepeatArgs() throw()
    : n_(), c_(), prefix_() {}

  RepeatArgs& set_n(size_t n) throw() { n_ = n; return *this; }
  RepeatArgs& set_c(char c) throw() { c_ = c; return *this; }
  RepeatArgs& set_prefix(const std::string& prefix) {
    prefix_ = prefix; return *this;
  }

  size_t n_;
  char c_;
  std::string prefix_;
};

struct RepeatResult {
  std::string r_;
};

void Repeat(const RepeatArgs& args, RepeatResult& res) throw() {
  res.r_ = args.prefix_;
  for (size_t i = 0; i < args.n_; ++i)
    res.r_ += args.c_;
}

int main() {
  RepeatResult res;
  Repeat(RepeatArgs().set_prefix("hello world").set_c('!').set_n(5), res);
  std::cout << res.r_ << std::endl;
  return 0;
}
Some people would probably prefer the with_ prefix as opposed to set_ for the builder's setters.

Note that the args must be a const RepeatArgs& type so that the arguments of Repeat() can be built using a temporary object, like seen in the example above.

I chose to leave the fields public (as the default for struct) because the fields of the Args struct directly correspond to the arguments that a function would have taken directly, hence the additional getter abstraction is not needed. The purpose of the setters is to allow chaining by returning reference to *this. Contrary to a typical builder pattern, there is no need for a final build() call because the reference can be used directly.

Not having build() means that argument checking must be done in the function that takes the Args struct. Some may argue that it is better to do argument checking in build(), but build() is supposed to return a reference to the Args struct, so there is no good way to report invalid arguments, sort of raising an exception or aborting in the form of assertion failure.

The default constructor in this case simply initializes fields by the default constructor of the respective types, with the plain old types size_t and char to 0lu and '\0', and std::string to the empty string. The default constructor could always give them different default values suitable for the Repeat() function in the constructor's initializer list.

The Repeat() function can also use the following interface if return value optimization is desired.
RepeatResult Repeat(const RepeatArgs& args) throw();
However, the benefit of the former interface is that the return value can be used to indicate error status. For example,
enum RepeatStatus {
  ok = 0,
  out_of_memory = 1,
  ...
};

RepeatStatus Repeat(const RepeatArgs& args, RepeatResult& res) throw();
The argument builder pattern can also be used for methods that have complex arguments.
class Xyzzy {
 public:
  struct FooArgs {
    ...
  };

  struct FooResult {
    ...
  };

  enum FooStatus {
    ...
  };

  FooStatus Foo(const FooArgs& args, FooResult& res) throw();
};
The declaration of the Foo() method is now very similar to that generated for a protocol buffer service, but better because of the use of fluent builder interface. The idea is that if protocol buffer could generate classes using the named argument builder pattern, then that would make protocol buffer even simpler to use and still very lightweight.
Post a Comment