Linearly Chained Factories and Move Semantics

114 Views Asked by At

I try to make chained factories.

The chain would use move semantics to avoid constructing unnecessary objects.

The chaining follows 3 rules:

  1. Unless a factory is flagged to make now, two factories can make an expression:

    • A_maker ^ B_maker<...> becomes an Exp<A_maker, B_maker>
  2. Unless a factory is flagged to make now, an expression and a factory makes a new expression:

    • Exp<...> ^ B_maker<...> becomes an Exp<Exp<...>, B_maker<...>> .
  3. A syntax of A_maker(..) ^ (B_maker(..).make()) would create a B<A> object.

I haven't implemented the logic to make B<B<A>>, or passing requirements from a later factory back to earlier factories. While creation of objects follows a the ordering of the factories, requirements are passed opposite to this ordering.

The problem with the current code is that it doesn't compile.

How to fix this?

Thanks.

Test (also at coliru)

#include <iostream>
#include <utility>

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

    int val_{-1};
};

template<typename P=A>
struct B {
    B(P&& p, int val)
            : p_(p), val_(val) {}

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

template<typename Top, typename Rest>
class Exp {
public:
    Exp(Top&& top, Rest&& rest)
            : top_(std::move(top)),
              rest_(std::move(rest)) {}

    template<typename Maker>
    auto operator^(const Maker& m) {
        if (m.make_now_) {
            return m.syn(make());
        } else {
            return append(m);
        }
    }

    auto make() { return rest_.syn(top_.syn); }

private:
    template<typename New_maker>
    Exp<Top, Exp<Rest, New_maker>> append(
            New_maker&& new_maker) {
        return Exp(top_, Exp(rest_, new_maker));
    }

    Top top_;
    Rest rest_;
};

class A_maker {
public:
    explicit A_maker(int val) : val_(val) {}

    auto make() { return syn(); }

    template<typename T>
    auto operator^(T&& other_maker) {
        return Exp<A_maker, T>(std::move(*this),
                               other_maker);
    }

private:
    A syn() { return A(val_); }

    int val_;

    template<typename T, typename R> friend
    class Exp;
};

template<typename P=A>
class B_maker {
    using self_type = B_maker<P>;
public:

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

    self_type&& make() {
        make_now_ = true;
        return std::move(*this);
    }

private:
    B<P> syn(P&& p) { return B(p, val_); }

    bool make_now_{false};
    int val_;

    template<typename T, typename R> friend
    class Exp;
};

int main() {
    B bba(B(A(0), 1), 2);
    auto x = A_maker(0) ^B_maker(1).make();
    return 0;
}

Compilation Error:

error: cannot bind rvalue reference of type 
    ‘B_maker<A>&&’ to lvalue of type ‘B_maker<A>’
         return Exp<A_maker, T>(std::move(*this), other_maker);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1

There are 1 best solutions below

0
On BEST ANSWER

You must use std::forward: https://godbolt.org/g/VfNk2G

You are in the function

template<typename T>
auto operator^(T&& other_maker) {
    return Exp<A_maker, T>(std::move(*this),
                           other_maker);
}

You are trying to call Exp::Exp(Top&& top, Rest&& rest) and the compiler complains that rest (i.e. other_maker) is of type B_maker<A> even though your function takes a B_maker<A>&& other_maker. The problem here is that as soon as you name that value, it is suddenly an lvalue again and no longer of type B_maker<A>&&. std::forward<T&&> fixes that by making it an rvalue reference again.