diff --git a/src/neo/memory.hpp b/src/neo/memory.hpp index a4687d6..56efbb2 100644 --- a/src/neo/memory.hpp +++ b/src/neo/memory.hpp @@ -3,6 +3,7 @@ #include "./concepts.hpp" #include "./declval.hpp" #include "./fwd.hpp" +#include "./query.hpp" #include "./type_traits.hpp" #include @@ -52,23 +53,34 @@ namespace _memory_detail { template constexpr bool has_value_type = requires { typename T::value_type; }; -template -constexpr bool has_get_allocator = requires(T & t) { t.get_allocator(); }; - } // namespace _memory_detail +/** + * @brief Query object type that obtains the allocator from an object + */ +struct get_allocator_q : query_interface { + static constexpr auto exec(const auto& obj) noexcept + requires requires { obj.get_allocator(); } + { + return obj.get_allocator(); + } +}; + +/** + * @brief Query object that obtains the allocator associated with an object + */ +inline constexpr get_allocator_q get_allocator; + template constexpr auto allocator_of(T&& t, auto alloc = std::allocator{}) noexcept { - if constexpr (_memory_detail::has_get_allocator) { - return t.get_allocator(); - } else if constexpr (_memory_detail::has_value_type) { - return rebind_alloc(alloc); - } else { - return alloc; - } + return query_or(get_allocator, t, alloc); } -template -using get_allocator_t = decltype(allocator_of(NEO_DECLVAL(T))); +/** + * @brief Match a type that is an allocator for the given object, according to + * std::uses_allocator_v + */ +template +concept allocator_for = std::uses_allocator_v; } // namespace neo diff --git a/src/neo/query.hpp b/src/neo/query.hpp new file mode 100644 index 0000000..f9e4ccf --- /dev/null +++ b/src/neo/query.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include "./concepts.hpp" +#include "./declval.hpp" +#include "./type_traits.hpp" + +namespace neo { + +/** + * @brief Match a query object + * + * @tparam Q A type that can be used as a query + * + * The semantics of the given object require that all instances are equivalent + * and that there exist an `operator()` that returns an appropriate query result. + */ +template +concept query_object = neo::copyable; + +/** + * @brief Match a query type object that can be executed on the given object + * + * @tparam Q A query type to be tested + * @tparam T The primary argument object to be queried + * @tparam Args Supplemental arguments to the query call + */ +template +concept valid_query_for = query_object and requires(const Q q, const T obj, Args&&... args) { + { q(obj, static_cast(args)...) } noexcept -> detail::can_reference; +}; + +/** + * @brief Obtain the result of executing the given query on an object + */ +template + requires valid_query_for +using query_t = decltype(NEO_DECLVAL(const Q&)(NEO_DECLVAL(const T&), NEO_DECLVAL(Args&&)...)); + +/** + * @brief Implement a query interface that supports a direct query and an indirect query + * + * @tparam Derived The derived type, or `void` to deduce the instance type + * + * When invoked, attempts to call a `exec()` static method on the derived class that + * should be constrained appropriately to execute the direct query. If the + * direct query via `exec()` is not supported, attempts to invoke `obj.query(*this, ...)` + * on the queried object. + */ +template +class query_interface { +private: + constexpr const Derived& _self() const noexcept { return static_cast(*this); } + +public: + template + constexpr decltype(auto) operator()(const Object& obj, Args&&... args) const noexcept + requires requires { Derived::exec(obj, NEO_FWD(args)...); } + { + return Derived::exec(obj, NEO_FWD(args)...); + } + + template + constexpr decltype(auto) operator()(const Object& obj, Args&&... args) const noexcept + requires requires(const Derived& q) { + obj.query(q, NEO_FWD(args)...); + requires not requires { Derived::exec(obj, NEO_FWD(args)...); }; + } + { + return obj.query(static_cast(*this), NEO_FWD(args)...); + } +}; + +#ifdef __cpp_explicit_this_parameter +template <> +class query_interface { +public: + template + constexpr decltype(auto) + operator()(this Self const&, const Object& obj, Args&&... args) noexcept + requires requires { Self::exec(obj, NEO_FWD(args)...); } + { + return Self::exec(obj, NEO_FWD(args)...); + } + + template + constexpr decltype(auto) + operator()(this Self const& self, const Object& obj, Args&&... args) noexcept + requires requires { + obj.query(self, NEO_FWD(args)...); + requires not requires { Self::exec(obj, NEO_FWD(args)...); }; + } + { + return obj.query(self, NEO_FWD(args)...); + } +}; +#endif + +template +constexpr decltype(auto) query_or(Q q, const T& obj, Default&& dflt) { + if constexpr (valid_query_for) { + return q(obj); + } else { + return static_cast(dflt); + } +} + +} // namespace neo diff --git a/src/neo/query.test.cpp b/src/neo/query.test.cpp new file mode 100644 index 0000000..69b7156 --- /dev/null +++ b/src/neo/query.test.cpp @@ -0,0 +1,13 @@ +#include "./query.hpp" +#include + +struct get_allocator : neo::query_interface { + static constexpr auto exec(auto const& obj) + requires requires { obj.get_allocator(); } + { + return obj.get_allocator(); + } +}; + +static_assert(neo::valid_query_for>); +static_assert(not neo::valid_query_for>);