2
\$\begingroup\$

I'm working on a web framework in C++ and there's this part that I need to do something like this:

using valves;

App app;
app.on( (get or head) and "/home"_path , [] (auto &req, auto& res) {
    // ...
});

(The code above is not a semi-code, it'll be like that exactly)

In order to implement the (get or head) and "/home"_path) part, I'm doing this: I'm calling those get and head and "/home"_path (which is just path("/home") valves.

Here's the code:

enum class logical_operators { AND, OR, XOR };

template <typename NextValve>
struct basic_valve {
    using next_valve_type =
        std::remove_reference_t<std::remove_cv_t<NextValve>>;

    next_valve_type next;
    logical_operators op;

    constexpr basic_valve(next_valve_type&& _next,
                          logical_operators op) noexcept
        : next(std::move(_next)), op(op) {}

    constexpr basic_valve(next_valve_type const& _next,
                          logical_operators op) noexcept
        : next(_next), op(op) {}

    constexpr basic_valve(basic_valve const& v) noexcept = default;
    constexpr basic_valve(basic_valve&& v) noexcept = default;

    constexpr basic_valve&
    operator=(basic_valve const& v) noexcept = default;
    constexpr basic_valve& operator=(basic_valve&&) noexcept = default;
};

template <>
struct basic_valve<void> {};

template <typename ValveType, typename NextValve = void>
class valve : public basic_valve<NextValve>, public ValveType {
  public:
    using type = ValveType;
    using next_valve_type = NextValve;

    using ValveType::ValveType;
    using basic_valve<NextValve>::basic_valve;
    constexpr valve() noexcept = default;

    /**
     * @tparam NewValveType
     * @param valve
     */
    template <typename NewValve>
    [[nodiscard]] constexpr auto set_next(NewValve&& v,
                                          logical_operators the_op) const
        noexcept {

        if constexpr (std::is_void_v<next_valve_type>) {
            // this part will only execute when the "next_valve_type" is
            // void

            // the first way (A<X, void> and B<Y, void> === A<X, B<Y, void>>
            return valve<ValveType, NewValve>(std::forward<NewValve>(v),
                                              the_op);
        } else {
            // this means this function has a "next" valve already,
            // so it goes to the next's next valve
            // this way we recursively create a valve type and return it.
            auto n = basic_valve<NextValve>::next.set_next(v, the_op);
            return valve<ValveType, decltype(n)>{n, this->op};
        }
    }

    template <typename NewValve>
    [[nodiscard]] constexpr auto operator&&(NewValve&& v) const noexcept {
        return set_next(std::forward<NewValve>(v), logical_operators::AND);
    }

    template <typename NewValve>
    [[nodiscard]] constexpr auto operator&(NewValve&& v) const noexcept {
        return set_next(std::forward<NewValve>(v), logical_operators::AND);
    }

    template <typename NewValve>
    [[nodiscard]] constexpr auto operator||(NewValve&& v) const noexcept {
        return set_next(std::forward<NewValve>(v), logical_operators::OR);
    }

    template <typename NewValve>
    [[nodiscard]] constexpr auto operator|(NewValve&& v) const noexcept {
        return set_next(std::forward<NewValve>(v), logical_operators::OR);
    }

    template <typename NewValve>
    [[nodiscard]] constexpr auto operator^(NewValve&& v) const noexcept {
        return set_next(std::forward<NewValve>(v), logical_operators::XOR);
    }

    template <typename Interface>
    [[nodiscard]] bool operator()(request_t<Interface> const& req) const
        noexcept {
        if constexpr (std::is_void_v<NextValve>) {
            return ValveType::operator()(req);
        } else {
            switch (basic_valve<NextValve>::op) {
            case logical_operators::AND:
                return ValveType::operator()(req) &&
                       basic_valve<NextValve>::next.operator()(req);
            case logical_operators::OR:
                return ValveType::operator()(req) ||
                       basic_valve<NextValve>::next.operator()(req);
            case logical_operators::XOR:
                return ValveType::operator()(req) ^
                       basic_valve<NextValve>::next.operator()(req);
            default:
                return false;
            }
        }
    }
};

struct method_condition {
  private:
    std::string_view method_string;

  public:
    constexpr method_condition(std::string_view str) noexcept
        : method_string(str) {}
    constexpr method_condition() noexcept = default;

    template <typename Interface>
    [[nodiscard]] bool operator()(request_t<Interface> const& req) const
        noexcept {
        return req.request_method() == method_string;
    }
};

struct method : public valve<method_condition> {
    using valve<method_condition>::valve;
};

struct empty_condition {
    template <typename Interface>
    [[nodiscard]] constexpr bool
    operator()(request_t<Interface> const& /* req */) const noexcept {
        return true;
    }
};

struct empty_t : public valve<empty_condition> {};
constexpr empty_t empty;

In the above code I haven't implemented the get, head, path and others yet but you get how they'll be implemented based on the two specializations of empty and method("GET".

The request is something like this:

template <typename Interface>
struct request_t {
  // ...
  std::string_view request_method() noexcept {
    // ...
  }
}

The required features of valves are:

  • These ((get && head) || path("/about")) should be calculated at compile time but the operator() will be run at run-time.
  • Should be as fast as it can be at run-time
  • No vtable (It's too slow isn't it?)

And I know I over-optimize things sometimes but I just can't help it :)

So, is there anyway I can make this better?

Code on compiler explorer: https://godbolt.org/z/5EB-GM

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Are you sure you don't have an extra ) after _path in your first example? The parenthesis don't balance out. \$\endgroup\$ Commented Nov 14, 2019 at 13:07
  • \$\begingroup\$ Do you have any unit tests for this code that you can post? \$\endgroup\$ Commented Nov 14, 2019 at 15:25
  • \$\begingroup\$ @pacmaninbw not at this point unfortunately! \$\endgroup\$ Commented Nov 15, 2019 at 18:38

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.