4.0. RareBuilder - TheNitesWhoSay/RareCpp GitHub Wiki

RareBuilder is a simple yet powerful implementation of the builder pattern for classes reflected using RareCpp. Run

struct MyObj
{
    int myInt;
    std::string myString;
    
    REFLECT(MyObj, myInt, myString)
};

int main()
{
    MyObj myObj = RareBuilder<MyObj>().myInt(1234).myString("asdf").build();
    std::cout << Json::out(myObj); // prints {"myInt":1234,"myString":"asdf"}
}

RareBuilder is aimed at providing a method of constructing and initializing reflected objects using the names of the members (similar to designated initializers) as well as avoiding the need to have multiple repetitive constructors for different valid sets of input.

RareBuilder is auto-complete friendly and strongly-typed, and of note, builders are not actually generated by the REFLECT macro, builders are an adaptive structure.

RareBuilder will default-construct a type, then allow .build() or some member-setter method to be called. The member setters, e.g. myInt(1234) or myString("asdf"), takes a const reference to the member type as input and assigns it to the member in the object - thus nearly anything you can safely use to initialize a variable of the members type can be passed as input to the member-setters.

The .build() method should be called in the same statement that opened the builder.

Validated Builders

A validate method will be called by the .build() method prior to returning if there is such a method on the built object. The validate method can be used to validate that a set of inputs passed into the builder resulted in an object in a valid state. This can also be used to perform any post-build initialization that, without a builder, might have been done in the body of a constructor.

The validate method should have the signature

void validate(); // or...
bool validate();

e.g. Run

class MyObj
{
    int a;
    int b;

public:
    REFLECT(MyObj, a, b)

    bool validate() { return a >= 0 && b >= 0; }
    /*void validate() { // Or use exceptions to indicate what was invalid
        if ( a < 0 || b < 0 )
            throw std::logic_error("a and b must both be non-negative!");
    }*/
};

The void method should throw an exception to indicate validation failure while the bool method should return false to indicate failure. Either way, .build() will throw an exception if validation fails. If exceptions were used you can provide & retrieve more information about what went wrong; while the bool method typically provides less info, it can be used much like asserts as a quick sanity-check/indication that there's an issue which needs to be debugged.

Member Availability

Public instance-data members are always available to the builder unless annotated with RareTs::BuilderIgnore.

Protected and private members are available if unignored provided that:

  1. A validate method is present on the object, or...
  2. A RareTs::Buildable class-level annotation is present on the object, or...
  3. A RareTs::BuilderIgnore member-level annotation is present on any member of the object

The requirement that an object is validated or somehow signals intent to be used with a builder helps to prevent accidental violation of class invariants when use of reflected objects and builders is ubiquitous.

Example of unavailable members:

class MyObj {
    int a = 0;
    int b = 0;

    REFLECT(MyObj, a, b)
};


int main() {
    MyObj buildable = RareBuilder<MyObj>().a(1).b(2).build(); // Compiler error, private members a and b are not available to the builder!
}

Example of class-level Buildable annotation:

NOTE(MyBuildable, RareTs::Buildable)
class MyBuildable
{
    int a = 0;
    int b = 0;

    REFLECT_NOTED(MyBuildable, a, b)
};

int main()
{
    MyBuildable buildable = RareBuilder<MyBuildable>().a(1).b(2).build();
    std::cout << Json::out(buildable); // prints {"a":1,"b":2}
}

Example of member-level BuilderIgnore annotation:

class MyBuildable
{
    int a = 0;

    NOTE(b, RareTs::BuilderIgnore)
    int b = 0;

    REFLECT(MyBuildable, a, b)
};

int main()
{
    //MyBuildable buildable = RareBuilder<MyBuildable>().b(1).build(); // Compiler-error!
    MyBuildable buildable = RareBuilder<MyBuildable>().a(1).build();
    std::cout << Json::out(buildable); // prints {"a":1,"b":0}
}
⚠️ **GitHub.com Fallback** ⚠️