4
\$\begingroup\$

I want to make factories that can chained together to create a pile of layers. One of the advantage of chained factories is that later factories can pass specifications to earlier factories. For example, if I want to make a tree on top of an array, I wouldn't know the correct size of the array until I know the size of the tree.

In the code I posted for review, the bottom layer of any pile must be an A (or any other class of memory layer in the actual library). A can only be the lowest layer in a pile.

Here is an example of chaining the factories.

// Create B above B above A
auto bba = B_maker(9) % B_maker(13) % A_maker(17);

A and B<...> both have move constructors.

I try to avoid writing each factory from scratch with CRTP.

The code is also at github.

Thanks.

Result:

ID: 2 Value: 9 Type: B<B<A> >
ID: 1 Value: 13 Type: B<A>
ID: 0 Value: 17 Type: A

demo.cpp:

#include <iostream>
#include <utility>
#include "chained_factory.h"
#include "test_classes.h"


template<typename Prev_maker=Null_maker>
class A_maker : public Foundation_factory<A_maker<Prev_maker>> {
public:
    static constexpr bool is_base_maker = true;
    explicit A_maker(int val, Prev_maker&& prev = Null_maker())
            : val_(val), prev_(std::move(prev)) {}

    template<typename T>
    auto rebind_prev(T&& prev) {
        return A_maker<T>(val_, std::forward<T&&>(prev));
    }

private:
    auto syn() { return A(val_); }
    Prev_maker prev_;
    int val_;
    friend Foundation_factory<A_maker<Prev_maker>>;
};


template<typename Prev_maker=Null_maker>
class B_maker : public Chaining_factory<B_maker<Prev_maker>> {
    using self_type = B_maker<Prev_maker>;
public:
    explicit B_maker(int val, Prev_maker&& prev = Null_maker())
            : val_(val), prev_(std::move(prev)) {}

    template<typename T>
    auto rebind_prev(T&& prev) {
        auto r = B_maker<T>(val_, std::forward<T&&>(prev));
        return r;
    }

private:
    template <typename T>
    auto syn(T&& from_next) { return B(std::forward<T&&>(from_next), val_); }
    Prev_maker prev_;
    int val_;
    friend Prev_maker;
    friend Chaining_factory<B_maker<Prev_maker>>;
};


int main() {
    auto bba = B_maker(9) % B_maker(13) % A_maker(17);
    std::cout << bba;
    return 0;
}

chain_factory.h:

#ifndef CHAINED_FACTORY_CHAINED_FACTORY_H
#define CHAINED_FACTORY_CHAINED_FACTORY_H

#include <iostream>
struct Null {
};

class Null_maker {
public:
    template <typename T>
    auto make(T&& x) { return std::forward<T&&>(x); }
    using target_type = Null;
};


template<typename Derived>
class Chaining_factory {
public:
    static constexpr bool is_base_maker = false;
    template<typename T>
    auto make(T&& from_next) {
        return derived()->prev_.make(derived()->syn(std::forward<T&&>(from_next)));
    }

    template<typename T>
    auto operator%(T&& other_maker) {
        auto result = other_maker.template rebind_prev<Derived>(
                std::move(*derived()));
        if constexpr (T::is_base_maker) {
            return result.make();
        } else {
            return result;
        }
    }

private:
    auto derived() { return static_cast<Derived*>(this); }
};

template<typename Derived>
class Foundation_factory {
public:

    static constexpr bool is_base_maker = true;

    auto make() {
        return derived()->prev_.make(derived()->syn());
    }

private:
    auto derived() { return static_cast<Derived*>(this); }
};

#endif //CHAINED_FACTORY_CHAINED_FACTORY_H

test_class.h:

#ifndef CHAINED_FACTORY_TEST_CLASSES_H
#define CHAINED_FACTORY_TEST_CLASSES_H

#include "type2name.h"
struct A {
    static constexpr int id = 0;

    explicit A(int val) : val_(val) {}

    int val_{-1};
};

template<typename P=A>
struct B {
    static constexpr int id = 1 + P::id;

    B(P&& p, int val)
            : p_(std::move(p)), val_(val) {}

    P p_;
    int val_{-1};
};

std::ostream& operator<<(std::ostream& os, const A& m) {
    return os << "ID: " << m.id << " "
              << "Value: " << m.val_ << " "
              << "Type: " << type2name<A>() << "\n";
}

template<typename P>
std::ostream& operator<<(std::ostream& os, const B<P>& m) {
    return os << "ID: " << m.id << " "
              << "Value: " << m.val_ << " "
              << "Type: " << type2name<B<P>>() << "\n"
              << m.p_;
}

#endif //CHAINED_FACTORY_TEST_CLASSES_H
\$\endgroup\$
  • \$\begingroup\$ I thought about the alternative: make_pile(A_maker(2, 3), B_maker(3, 4), ...) and each maker would not track earlier makers. But that means passing requirements between makers need a stream interface, and buffer. That means the compiler have 0 chance to optimize away the construction by the factories. \$\endgroup\$ – R zu May 16 '18 at 4:34
  • \$\begingroup\$ Probably need a doubly linked list of makers if I want to propagate requirements between the factories before making. Before the chain is complete, I can't pass requirements unless I use a buffer interface. After the chain is complete, I need to backtrack to the first maker and then propagate requirements from 1st to last maker. Finally, the object is made from the last maker to the 1st maker as an onion of layers. \$\endgroup\$ – R zu May 16 '18 at 16:38
  • \$\begingroup\$ Can't make a doubly linked list if I track both prev and next by template parameters. \$\endgroup\$ – R zu May 16 '18 at 17:24
  • \$\begingroup\$ In the end, I make a chain of bracktracker object to hold a temporary chain of forward linkages that is built from the backward linkage in each maker. Then, I cascade requirements from emitted by each maker to later makers. \$\endgroup\$ – R zu May 16 '18 at 18:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.