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\$
4
  • 1
    \$\begingroup\$ Are you sure you don't have an extra ) after _path in your first example? The parenthesis don't balance out. \$\endgroup\$
    – nwp
    Nov 14 '19 at 13:07
  • \$\begingroup\$ yes, good catch, fixed it; thanks \$\endgroup\$
    – moisrex
    Nov 14 '19 at 13:17
  • \$\begingroup\$ Do you have any unit tests for this code that you can post? \$\endgroup\$
    – pacmaninbw
    Nov 14 '19 at 15:25
  • \$\begingroup\$ @pacmaninbw not at this point unfortunately! \$\endgroup\$
    – moisrex
    Nov 15 '19 at 18:38

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.