Fluent Builder Pattern

Sven van Huessen | Nov 4, 2025

💻 Language: C++


Fluent Builder Pattern

For ImReflect, I wanted a way to create custom settings and responses for each type. So that users could do something like this:

ImConfig config;
config.push<int>()
            .min(0)
            .max(100)
            .as_slider()
        .pop();

To do this, I created an open-source C++ GitHub library called “Fluent Builder Pattern”. This gives me a generic scope system that allows me to create nested settings and responses for any type.

This system relies on a single struct called scope.

template<template<class> class BaseTemplate>
struct scope {
}

scope takes in an unfinished templated type. The way you can use scope is by defining an empty templated struct and use that as the BaseTemplate parameter. For example, type settings:

template<class T, class ENABLE = void>
struct type_settings : svh::scope<type_settings> {
    // Default empty implementation
};

scope<type_settings> settings_scope;

scope has a function called push<T>, which returns the base template specialized for T. (in this case type_settings<T>):

template<class T>
BaseTemplate<T>& push() {
    // Create new settings for type T
    // Store it internally
    // Return reference to the created settings
}

So if the user calls settings_scope.push<int>(), it will return a reference to type_settings<int>. But because the int specialization doesn’t exist yet, it will return the generic templated implementation. And Since the generic type_settings inherits from scope<type_settings>, the user can keep calling push<T> on the returned object to create nested scopes:

auto& settings = settings_scope.push<int>().push<float>().push<bool>();

Now this is cool and all, but we want some sort of way to set specific settings for each type. For this we can implement specializations of type_settings for specific types:

template<>
struct type_settings<int> : svh::scope<type_settings> {
    int min_value = 0;
    int max_value = 100;

    type_settings<int>& min(int min) {
        min_value = min;
        return *this;
    }

    type_settings<int>& max(int max) {
        max_value = max;
        return *this;
    }
};

Now whenever the user calls settings_scope.push<int>(), it will return a reference to type_settings<int>, which has the min() and max() functions. But it also still has the push<T> function from the scope base class, allowing for nested scopes:

settings_scope.push<int>()
    .min(0)
    .max(50)
    .push<float>().push<bool>();

This allows me to create a very flexible and extensible settings for any type I want. I simply implement a type_setting for the type I want, and whenever the user calls push<T>(), with that type ,it reutns the implementation with the builder functions.

And to avoid code duplication I created this min max functionality as a mixin (simplified):

template<class T>
struct min_max_mixin {
    T min_value;
    T max_value;

    min_max_mixin() : min_value(std::numeric_limits<T>::lowest()),
                      max_value(std::numeric_limits<T>::max()) {}

    type_settings<T>& min(T min) {
        min_value = min;
        return *this;
    }

    type_settings<T>& max(T max) {
        max_value = max;
        return *this;
    }
};

Then I can just inherit from this mixin in the type_settings<int> specialization:

template<>
struct type_settings<int> : svh::scope<type_settings>, min_max_mixin<int> {
    // ...
};

So now whenever the user calls .push<int>(), it will return a type_settings<int> reference that inherits from min_max_mixin. Allowing the user to call the builder pattern functions min() and max().

To make it even more generic, instead of making a specialization for every numeric type, I can use SFINAE to create a single specialization for all numeric types:

template<typename T> /* All numbers except bool */
constexpr bool is_numeric_v = (std::is_integral_v<T> || std::is_floating_point_v<T>) && !is_bool_v<T>;
template<typename T>
using enable_if_numeric_t = std::enable_if_t<is_numeric_v<std::remove_cv_t<T>>, void>;

/* ========================= all integral types except bool ========================= */
template<typename T>
struct type_settings<T, enable_if_numeric_t<T>> : scope<type_settings>, min_max_mixin<T> {
};

This type_settings specialization will be used for all numeric types (int, float, double, etc.) except for booleans since those have different settings in ImReflect.

Result

With this system now in place, users are able to customize very specific settings. For example:

svh::scope<type_settings> config;
config.push<int>()
        .min(-100)
        .max(100)
    .pop()
    .push<MyStruct>()
    	.push<int>()
            .min(0)
    		.max(20)
    	.pop()
    .pop();
// Access the nested settings
auto& root_int = config.get<int>();  // min=-100, max=100
auto& nested_int = config.get<MyStruct>().get<int>(); // min=0, max=20

Additionally, users can specific custom settings for their type by simply writing their own customization (simplified):

struct Transform {/* ... */};

template<>
struct type_settings<Transform> : scope<type_settings> {
    bool some_setting = false;

    type_settings<Transform> enable_setting() { 
        some_setting = true; 
        return *this;
    }

    type_settings<Transform> disable_setting() { 
        some_setting = false; 
        return *this;
    }

    bool get_setting() const {
        return seom_setting;
    }
}


int main() {
    svh::scope<type_settings> config;
    config.push<Transform>()
                .enable_setting()
                .disable_setting()
                .enable_setting()
            .pop();

    config.get<Transform>().get_setting(); // return true
}

This gives users full control of their own types without having to touch the library.