-
Jan Trávníček authored
concepts are language native construct that was introduced to replace enable_if (SFINAE)
Jan Trávníček authoredconcepts are language native construct that was introduced to replace enable_if (SFINAE)
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
dry-comparisons.hpp 12.61 KiB
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org>
*
* Original source https://github.com/rollbear/dry-comparisons
*
* @author Björn Fahller
* @modified Jan Trávníček
*/
#pragma once
#include <utility>
#include <type_traits>
#include <tuple>
#include <functional>
#include <iosfwd>
namespace ext {
namespace internal {
template <typename, typename = void>
struct printable;
template <typename ... Ts>
struct printable<std::tuple<Ts...>, std::void_t<decltype(std::declval<std::ostream&>() << std::declval<const Ts&>())...>>
{
using type = void;
};
template <typename T>
using printable_t = typename printable<T>::type;
template <typename F, typename ... Args>
struct bound
{
using RT = std::invoke_result_t<F, Args...>;
template <typename F_, typename ... As>
constexpr bound(F_&& f_, As&& ... as) : f(std::forward<F_>(f_)), args(std::forward<As>(as)...) {}
constexpr operator RT() const
noexcept(std::is_nothrow_invocable_v<const F&, const Args&...>)
{
return std::apply(f, args);
}
F f;
std::tuple<Args...> args;
};
template <typename ... Ts>
struct binder
{
std::tuple<Ts...> values;
template <typename T, typename F>
constexpr bound<T, Ts...> bind(const F& f) const
{
return std::apply([&](auto&& ... vs){return bound<T, Ts...>{f, vs...};}, values);
}
};
class logical_tuple_base { };
template <typename ... Ts>
class logical_tuple : std::tuple<Ts...>, public logical_tuple_base
{
using tuple = std::tuple<Ts...>;
constexpr const tuple& self() const { return *this; }
protected:
using tuple::tuple;
template <typename F>
constexpr auto or_all(F&& f) const
{
return std::apply([&](const auto& ... v) { return (f(v) || ...);}, self());
}
template <typename F>
constexpr auto and_all(F&& f) const
{
return std::apply([&](const auto& ... v) { return (f(v) && ...);}, self());
}
template <typename RT, typename ... Args>
constexpr RT bind(Args&& ... args) const {
return std::apply([&](auto&&... f) {
binder<Args...> b{{args...}};
return RT{b.template bind<Ts>(std::forward<decltype(f)>(f))...}; }, self());
}
template <typename Char, typename Traits>
std::basic_ostream<Char, Traits>& print(const Char* label, std::basic_ostream<Char, Traits>& os) const
{
os << label << '{';
std::apply([&os](const auto& ... v) {
int first = 1;
((os << &","[std::exchange(first, 0)] << v),...);
}, self());
return os << '}';
}
};
}
template <typename ... T>
class any_of : internal::logical_tuple<T...>
{
using internal::logical_tuple<T...>::or_all;
using internal::logical_tuple<T...>::and_all;
public:
using internal::logical_tuple<T...>::logical_tuple;
template <typename U>
constexpr auto operator==(const U& u) const
noexcept(noexcept(((std::declval<const T&>() == u) || ...)))
-> decltype(((std::declval<const T&>() == u) || ...))
{
return or_all([&](auto&& v) { return v == u;});
}
template <typename U>
constexpr auto operator!=(const U& u) const
noexcept(noexcept(((std::declval<const T&>() != u) && ...)))
-> decltype(((std::declval<const T&>() != u) && ...))
{
return and_all([&](auto v) { return v != u;});
}
template <typename U>
constexpr auto operator<(const U& u) const
noexcept(noexcept(((std::declval<const T&>() < u) || ...)))
-> decltype(((std::declval<const T&>() < u) || ...))
{
return or_all([&](auto&& v){ return v < u;});
}
template <typename U>
constexpr auto operator<=(const U& u) const
noexcept(noexcept(((std::declval<const T&>() <= u) || ...)))
-> decltype(((std::declval<const T&>() <= u) || ...))
{
return or_all([&](auto&& v){ return v <= u;});
}
template <typename U>
constexpr auto operator>(const U& u) const
noexcept(noexcept(((std::declval<const T&>() > u) || ...)))
-> decltype(((std::declval<const T&>() > u) || ...))
{
return or_all([&](auto&& v) { return v > u;});
}
template <typename U>
constexpr auto operator>=(const U& u) const
noexcept(noexcept(((std::declval<const T&>() >= u) || ...)))
-> decltype(((std::declval<const T&>() >= u) || ...))
{
return or_all([&](auto&& v) { return v >= u;});
}
template <typename V = std::tuple<T...>, typename = internal::printable_t<V>>
friend std::ostream& operator<<(std::ostream& os, const any_of& self)
{
return self.print("any_of", os);
}
constexpr explicit operator bool() const
noexcept(noexcept((std::declval<const T&>() || ...)))
{
return or_all([](auto&& v) { return v;});
}
template <typename ... Ts>
requires ( std::conjunction_v<std::is_copy_constructible<Ts>...>&& std::conjunction_v<std::is_invocable<T, Ts...>...> )
constexpr auto operator()(Ts&& ... ts) const
noexcept(std::conjunction_v<std::is_nothrow_move_constructible<T>...> && std::conjunction_v<std::is_nothrow_copy_constructible<Ts>...>)
-> any_of<internal::bound<T, Ts...>...>
{
using RT = any_of<internal::bound<T, Ts...>...>;
return this->template bind<RT>(std::forward<Ts>(ts)...);
}
};
template <typename ... T>
class none_of : internal::logical_tuple<T...>
{
using internal::logical_tuple<T...>::or_all;
using internal::logical_tuple<T...>::and_all;
public:
using internal::logical_tuple<T...>::logical_tuple;
template <typename U>
constexpr auto operator==(const U& u) const
noexcept(noexcept(!((std::declval<const T&>() == u) || ...)))
-> decltype(!((std::declval<const T&>() == u) || ...))
{
return !or_all([&](auto&& v) { return v == u;});
}
template <typename U>
constexpr auto operator!=(const U& u) const
noexcept(noexcept(!((std::declval<const T&>() != u) && ...)))
-> decltype(!((std::declval<const T&>() != u) && ...))
{
return !and_all([&](auto && v){return v != u;});
}
template <typename U>
constexpr auto operator<(const U& u) const
noexcept(noexcept(!((std::declval<const T&>() < u) || ...)))
-> decltype(!((std::declval<const T&>() < u) || ...))
{
return !or_all([&](auto&& v){ return v < u;});
}
template <typename U>
constexpr auto operator<=(const U& u) const
noexcept(noexcept(!((std::declval<const T&>() <= u) || ...)))
-> decltype(!((std::declval<const T&>() <= u) || ...))
{
return !or_all([&](auto&& v){ return v <= u;});
}
template <typename U>
constexpr auto operator>(const U& u) const
noexcept(noexcept(!((std::declval<const T&>() > u) || ...)))
-> decltype(!((std::declval<const T&>() > u) || ...))
{
return !or_all([&](auto&& v) { return v > u;});
}
template <typename U>
constexpr auto operator>=(const U& u) const
noexcept(noexcept(!((std::declval<const T&>() >= u) || ...)))
-> decltype(!((std::declval<const T&>() >= u) || ...))
{
return !or_all([&](auto&& v){ return v >= u;});
}
template <typename V = std::tuple<T...>, typename = internal::printable_t<V>>
friend std::ostream& operator<<(std::ostream& os, const none_of& self)
{
return self.print("none_of", os);
}
constexpr explicit operator bool() const
noexcept(noexcept(!(std::declval<const T&>() || ...)))
{
return !or_all([](auto&& v) { return v;});
}
template <typename ... Ts>
requires ( std::conjunction_v<std::is_copy_constructible<Ts>...>&&std::conjunction_v<std::is_invocable<T, Ts...>...> )
constexpr auto operator()(Ts&& ... ts) const
noexcept( std::conjunction_v<std::is_nothrow_move_constructible<T>...> && std::conjunction_v<std::is_nothrow_copy_constructible<Ts>...>)
-> none_of<internal::bound<T, Ts...>...>
{
using RT = none_of<internal::bound<T, Ts...>...>;
return this->template bind<RT>(std::forward<Ts>(ts)...);
}
};
template <typename ... T>
class all_of : internal::logical_tuple<T...>
{
using internal::logical_tuple<T...>::or_all;
using internal::logical_tuple<T...>::and_all;
public:
using internal::logical_tuple<T...>::logical_tuple;
template <typename U>
constexpr auto operator==(const U& u) const
noexcept(noexcept(((std::declval<const T&>() == u) && ...)))
-> decltype(((std::declval<const T&>() == u) && ...))
{
return and_all([&](auto&& v){ return v == u;});
}
template <typename U>
constexpr auto operator!=(const U& u) const
noexcept(noexcept(((std::declval<const T&>() != u) || ...)))
-> decltype(((std::declval<const T&>() != u) || ...))
{
return or_all([&](auto&& v){return v != u;});
}
template <typename U>
constexpr auto operator<(const U& u) const
noexcept(noexcept(((std::declval<const T&>() < u) && ...)))
-> decltype(((std::declval<const T&>() < u) && ...))
{
return and_all([&](auto&& v){ return v < u;});
}
template <typename U>
constexpr auto operator<=(const U& u) const
noexcept(noexcept(((std::declval<const T&>() <= u) && ...)))
-> decltype(((std::declval<const T&>() <= u) && ...))
{
return and_all([&](auto&& v){ return v <= u;});
}
template <typename U>
constexpr auto operator>(const U& u) const
noexcept(noexcept(((std::declval<const T&>() > u) && ...)))
-> decltype(((std::declval<const T&>() > u) && ...))
{
return and_all([&](auto&& v){ return v > u;});
}
template <typename U>
constexpr auto operator>=(const U& u) const
noexcept(noexcept(((std::declval<const T&>() >= u) && ...)))
-> decltype(((std::declval<const T&>() >= u) && ...))
{
return and_all([&](auto&& v){ return v >= u;});
}
template <typename V = std::tuple<T...>, typename = internal::printable_t<V>>
friend std::ostream& operator<<(std::ostream& os, const all_of& self)
{
return self.print("all_of", os);
}
constexpr explicit operator bool() const
noexcept(noexcept((std::declval<const T&>() && ...)))
{
return and_all([](auto&& v) -> bool { return v;});
}
template <typename ... Ts>
requires ( std::conjunction_v<std::is_copy_constructible<Ts>...>&& std::conjunction_v<std::is_invocable<T, Ts...>...> )
constexpr auto operator()(Ts&& ... ts) const
noexcept( std::conjunction_v<std::is_nothrow_move_constructible<T>...> && std::conjunction_v<std::is_nothrow_copy_constructible<Ts>...>)
-> all_of<internal::bound<T, Ts...>...>
{
using RT = all_of<internal::bound<T, Ts...>...>;
return this->template bind<RT>(std::forward<Ts>(ts)...);
}
};
template <typename U, typename T>
requires ( std::is_base_of_v<internal::logical_tuple_base, T> && ! std::is_base_of_v < internal::logical_tuple_base, U > )
constexpr auto operator==(const U& u, const T& a)
noexcept(noexcept(a == u))
{
return a == u;
}
template <typename U, typename T >
requires ( std::is_base_of_v<internal::logical_tuple_base, T> && ! std::is_base_of_v < internal::logical_tuple_base, U > )
constexpr auto operator!=(const U& u, const T& a)
noexcept(noexcept(a != u))
{
return a != u;
}
template <typename U, typename T >
requires ( std::is_base_of_v<internal::logical_tuple_base, T> && ! std::is_base_of_v < internal::logical_tuple_base, U > )
constexpr auto operator>(const U& u, const T& a)
noexcept(noexcept(a < u))
{
return a.operator < ( u );
}
template <typename U, typename T >
requires ( std::is_base_of_v<internal::logical_tuple_base, T> && ! std::is_base_of_v < internal::logical_tuple_base, U > )
constexpr auto operator>=(const U& u, const T& a)
noexcept(noexcept(a <= u))
{
return a <= u;
}
template <typename U, typename T >
requires ( std::is_base_of_v<internal::logical_tuple_base, T> && ! std::is_base_of_v < internal::logical_tuple_base, U > )
constexpr auto operator<(const U& u, const T& a)
noexcept(noexcept(a > u))
{
return a.operator > ( u );
}
template <typename U, typename T >
requires ( std::is_base_of_v<internal::logical_tuple_base, T> && ! std::is_base_of_v < internal::logical_tuple_base, U > )
constexpr auto operator<=(const U& u, const T& a)
noexcept(noexcept(a >= u))
{
return a >= u;
}
template <typename ... T>
any_of(T&& ...) -> any_of<T...>;
template <typename ... T>
none_of(T&& ...) -> none_of<T...>;
template <typename ... T>
all_of(T&& ...) -> all_of<T...>;
}