libcruft-util/concepts.hpp

293 lines
8.0 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2020, Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include <type_traits>
#include <iterator>
#include <utility>
#include <functional>
///////////////////////////////////////////////////////////////////////////////
/// A minimal implementation of the standard concept library for use with
/// compilers that lack it.
///
/// Portions of this code are adapted from the standard and as such do not
/// fall under the top copyright notice.
///
/// clang#xxx: Remove me when clang includes an implementation of these.
namespace cruft::concepts {
template <typename T>
concept floating_point = std::is_floating_point_v<T>;
template <typename T>
concept integral = std::is_integral_v<T>;
template <typename T>
concept signed_integral = integral<T> && std::is_signed_v<T>;
template <typename T>
concept unsigned_integral = integral<T> && std::is_unsigned_v<T>;
template <typename T>
concept destructible = std::is_nothrow_destructible_v<T>;
template <class T, typename ...ArgsT>
concept constructible_from = destructible<T> && std::is_constructible_v<T, ArgsT...>;
template <class T>
concept default_constructible = constructible_from<T>;
template <class T>
concept copy_constructible = std::is_copy_constructible_v<T>;
template <class T>
concept move_constructible = std::is_move_constructible_v<T>;
template <typename A, typename B>
concept same_as = std::is_same_v<A, B> and std::is_same_v<B, A>;
template <typename From, typename To>
concept convertible_to =
std::is_convertible_v<From, To> &&
requires (From (&f)())
{
static_cast<To> (f());
};
template <
typename LHS,
typename RHS
>
concept assignable_from =
std::is_lvalue_reference_v<LHS> &&
// clang#xxx: common_reference_t is too annoying to implement as
// a temporary fix for this temporary fix. Reintroduce this when
// clang knows about common_reference_t.
// std::common_reference_with<
// std::remove_reference_t<LHS> const&,
// std::remove_reference_t<RHS> const&
// > &&
requires(LHS lhs, RHS&& rhs)
{
{ lhs = std::forward<RHS>(rhs) } -> same_as<LHS>;
};
template <typename T>
concept swappable = requires (T &&a, T &&b) { std::swap (a, b); };
template < class T >
concept movable =
std::is_object_v<T> &&
move_constructible<T> &&
assignable_from<T&, T> &&
swappable<T>;
template <typename T>
concept boolean =
movable<std::remove_cvref_t<T>> &&
requires (
std::remove_reference_t<T> const& b1,
std::remove_reference_t<T> const& b2,
bool const a
)
{
{ b1 } -> convertible_to<bool>;
{ !b1 } -> convertible_to<bool>;
{ b1 && b2 } -> same_as<bool>;
{ b1 && a } -> same_as<bool>;
{ a && b2 } -> same_as<bool>;
{ b1 || b2 } -> same_as<bool>;
{ b1 || a } -> same_as<bool>;
{ a || b2 } -> same_as<bool>;
{ b1 == b2 } -> convertible_to<bool>;
{ b1 == a } -> convertible_to<bool>;
{ a == b2 } -> convertible_to<bool>;
{ b1 != b2 } -> convertible_to<bool>;
{ b1 != a } -> convertible_to<bool>;
{ a != b2 } -> convertible_to<bool>;
};
template <typename T>
concept equality_comparable =
requires (
std::remove_reference_t<T> const &a,
std::remove_reference_t<T> const &b
)
{
{ a == b } -> boolean;
{ a != b } -> boolean;
{ b == a } -> boolean;
{ b != a } -> boolean;
};
template <typename FunctionT, typename ...ArgsT>
concept invocable =
requires (FunctionT &&function, ArgsT &&...args)
{
std::invoke (
std::forward<FunctionT> (function),
std::forward<ArgsT> (args)...
);
};
template <typename FunctionT, typename ...ArgsT>
concept regular_invocable = invocable<FunctionT, ArgsT...>;
template <typename FunctionT, typename ...ArgsT>
concept predicate =
regular_invocable<FunctionT, ArgsT...> &&
boolean<std::invoke_result_t<FunctionT, ArgsT...>>;
}
///////////////////////////////////////////////////////////////////////////////
// C++ named requirements
namespace cruft::concepts {
/// Corresponds to the "Container" named requirement.
template <class T>
concept container =
default_constructible<T> &&
copy_constructible<T> &&
move_constructible<T> &&
destructible<T> &&
equality_comparable<T> &&
requires (T a, T b)
{
typename T::value_type;
typename T::reference;
typename T::const_reference;
typename T::iterator;
typename T::const_iterator;
typename T::difference_type;
typename T::size_type;
{ a = b } -> same_as<T&>;
{ a = std::move (b) } -> same_as<T&>;
{ a.begin () } -> same_as<typename T::iterator>;
{ a.end () } -> same_as<typename T::iterator>;
{ a.cbegin () } -> same_as<typename T::const_iterator>;
{ a.cend () } -> same_as<typename T::const_iterator>;
{ a.swap (b) } -> same_as<void>;
{ std::swap (a, b) } -> same_as<void>;
{ a.size () } -> same_as<typename T::size_type>;
{ a.max_size () } -> same_as<typename T::size_type>;
{ a.empty () } -> boolean;
};
template <typename T>
concept move_assignable =
requires (T a, T b)
{
{ a = std::move (b) } -> same_as<T&>;
};
template <typename T>
concept copy_assignable =
move_assignable<T> &&
requires (T a, T b)
{
{ a = b } -> same_as<T&>;
};
template <typename T>
concept legacy_iterator =
copy_constructible<T> &&
copy_assignable<T> &&
destructible<T> &&
swappable<T> &&
requires (T t)
{
typename std::iterator_traits<T>::value_type;
typename std::iterator_traits<T>::difference_type;
typename std::iterator_traits<T>::reference;
typename std::iterator_traits<T>::pointer;
typename std::iterator_traits<T>::iterator_category;
{ *t };
{ ++t } -> same_as<T&>;
};
template <typename T>
concept legacy_input_iterator =
legacy_iterator<T> &&
equality_comparable<T> &&
requires (T a, T b)
{
typename std::iterator_traits<T>::reference;
typename std::iterator_traits<T>::value_type;
{ a != b } -> boolean;
{ ++a } -> same_as<T&>;
{ a++ };
};
}
///////////////////////////////////////////////////////////////////////////////
// Some handy non-standard concepts
#include <tuple>
namespace cruft::concepts {
template <typename T>
concept arithmetic = std::is_arithmetic_v<T>;
template <typename T>
concept scalar = std::is_scalar_v<T>;
template <typename T>
concept enumeration = std::is_enum_v<T>;
/// A type that supports arithmetic operators.
template <typename T>
concept numeric = requires (T t)
{
{ t * t } -> convertible_to<T>;
{ t / t } -> convertible_to<T>;
{ t - t } -> convertible_to<T>;
{ t + t } -> convertible_to<T>;
};
/// Anything that can be looped over using begin/end
template <typename T>
concept iterable = requires (T t)
{
{ std::begin (t) } -> legacy_iterator;
{ std::end (t) } -> legacy_iterator;
};
/// A class that supports tuple manipulators.
template <typename T>
concept tuple = requires (T a, T b)
{
{ std::tuple_element<0,T> {} };
{ std::tuple_size<T>::value } -> convertible_to<std::size_t>;
{ std::tuple_cat (a, b) };
};
}