Dmitry Fink
Dmitry Fink

Reputation: 1062

Chained builder pattern in C++

I am trying to create a a chained builder in C++ as follows:

#include <string>
#include <vector>

using std::string;
using std::vector;

class AbstractBuilder {
 protected:
    AbstractBuilder&
    add_field(string name, string value) {
        // blabla, add field to json structure
        return *this;
    }

};

class Event {
 public:
  class Builder : public AbstractBuilder {
   public:
    Builder& event(string event) {
      return add_field("event", event);
    }
    Builder& payload(string payload) {
      return add_field("payload", payload);
    }
  };
};

Event::Builder builder = Event::Builder().event("test");

The error I am getting is:

gcc test.cpp
test.cpp:22:14: error: non-const lvalue reference to type 'Event::Builder' cannot bind to a value of unrelated type 'AbstractBuilder'
      return add_field("event", event);
             ^~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:25:14: error: non-const lvalue reference to type 'Event::Builder' cannot bind to a value of unrelated type 'AbstractBuilder'
      return add_field("payload", payload);
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.

I am a C++ noob, and don't quite understand the issue and how to resolve it. AFAIU the object returned from the add_field in the Event::Builder is going to be a reference to self, of type Event::Builder, not an abstract temp/rvalue. What am I missing? What might be a better way to achieve this (build a set of strong typed builders for some json structures). Any existing examples are welcome.

Thanks

Upvotes: 0

Views: 1544

Answers (1)

JaMiT
JaMiT

Reputation: 16963

AFAIU the object returned from the add_field in the Event::Builder is going to be [...]

You are applying higher level reasoning. What is the compiler allowed to assume? It sees that add_field() returns AbstractBuilder& and you are trying to return that (from event() and payload()) as a Builder&. How can this be done? In general, it cannot since not every AbstractBuilder is a Builder.

If you are absolutely certain that add_field will return *this (even after future modifications to the code), then you can explicitly tell the compiler to assume that the returned reference is a sub-object of a Builder.

return static_cast<Builder&>(add_field("event", event));

This is fragile though, since the compiler cannot detect if your assumption breaks (in the future). You might want to add a runtime assertion to detect breakage. Less fragile would be to force these functions to return *this:

add_field("event", event);
return *this;

This has its own drawback in that your code repeats itself. (If you ever change the semantics of what these functions return, you'll have multiple places to change.) There is a judgment call to be made as to which approach is preferable, or perhaps if the overall design should be rethought.

Upvotes: 2

Related Questions