333 lines
9.1 KiB
C++
333 lines
9.1 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++ };
|
|
};
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Trivial wrappers around traits
|
|
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>;
|
|
|
|
template <typename T>
|
|
concept pointer = std::is_pointer_v<T>;
|
|
|
|
template <typename T>
|
|
concept reference = std::is_reference_v<T>;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Some handy non-standard concepts
|
|
#include <tuple>
|
|
|
|
namespace cruft::concepts {
|
|
/// Tests if the type has all typedefs required for use with
|
|
/// std::iterator_traits.
|
|
template <typename T>
|
|
concept supports_iterator_traits = requires
|
|
{
|
|
typename T::difference_type;
|
|
typename T::value_type;
|
|
typename T::reference;
|
|
typename T::iterator_category;
|
|
|
|
// C++20 defines `pointer` as void if it's not present.
|
|
#if __cplusplus <= 201703L
|
|
typename T::pointer;
|
|
#endif
|
|
};
|
|
|
|
|
|
template <
|
|
typename ContainerT,
|
|
typename IndexT = std::size_t
|
|
>
|
|
concept supports_indexing = requires (ContainerT &t, IndexT idx) {
|
|
{ t[idx] };
|
|
};
|
|
|
|
|
|
/// 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;
|
|
{ std::cbegin (t) } -> legacy_iterator;
|
|
{ std::cend (t) } -> legacy_iterator;
|
|
};
|
|
|
|
|
|
/// A class that supports tuple manipulators.
|
|
template <typename T>
|
|
concept tuple = requires (T a, T b)
|
|
{
|
|
// We should be checking this, but it fails for zero length tuples and
|
|
// it's kind of a low priority right now.
|
|
// { std::tuple_element<0,T> {} };
|
|
{ std::tuple_size<T>::value } -> convertible_to<std::size_t>;
|
|
{ std::tuple_cat (a, b) };
|
|
};
|
|
}
|