concepts: split and remove C++20 re-implementations

This commit is contained in:
Danny Robson 2022-03-18 11:38:30 +10:00
parent e96ee81c03
commit 6ed70a4839
12 changed files with 215 additions and 324 deletions

View File

@ -294,6 +294,7 @@ list (
colour.hpp
concepts.hpp
concepts/clock.hpp
concepts/string.hpp
container.hpp
coord.hpp
coord/fwd.hpp

View File

@ -9,7 +9,7 @@
#pragma once
#include "concepts.hpp"
#include "concepts/traits.hpp"
#include "debug/assert.hpp"
#include "platform.hpp"
@ -74,8 +74,8 @@ namespace cruft::cast {
/// Identity casts are allowed so as to simplify the use of this routine
/// in template code.
template <
concepts::arithmetic NarrowT,
concepts::arithmetic WideT
concepts::traits::arithmetic NarrowT,
concepts::traits::arithmetic WideT
>
requires
(std::is_signed_v<NarrowT> == std::is_signed_v<WideT>) &&
@ -150,8 +150,8 @@ namespace cruft::cast {
///
/// Runtime checks will be compiled out if NDEBUG is defined.
template <
concepts::pointer DstT,
concepts::pointer SrcT
concepts::traits::pointer DstT,
concepts::traits::pointer SrcT
>
DstT
alignment (SrcT src)
@ -172,7 +172,7 @@ namespace cruft::cast {
/// the converted value. Note: this is only a debug-time check and is
/// compiled out in optimised builds.
template <
concepts::pointer T,
concepts::traits::pointer T,
typename V
>
T
@ -188,7 +188,7 @@ namespace cruft::cast {
//-------------------------------------------------------------------------
template <
concepts::reference T,
concepts::traits::reference T,
typename V
>
T

View File

@ -38,7 +38,7 @@ namespace cruft::cmdopt2 {
bind (ValueT &ref)
{
CHECK (!acceptor1);
if constexpr (std::is_same_v<ValueT, std::string>) {
if constexpr (std::is_same_v<ValueT, std::string> or std::is_same_v<ValueT, char const*>) {
acceptor1 = [&ref] (std::string_view str) { ref = str; };
} else {
acceptor1 = [&ref] (std::string_view str) { ref = parse::from_string<ValueT> (str); };

View File

@ -1,5 +1,7 @@
#include "./parser.hpp"
#include "./arg.hpp"
using cruft::cmdopt2::parser;

View File

@ -8,7 +8,7 @@
#pragma once
#include "./arg.hpp"
#include "./fwd.hpp"
#include <iosfwd>
#include <vector>

View File

@ -8,268 +8,12 @@
#pragma once
#include <type_traits>
#include <iterator>
#include <utility>
#include <functional>
#include "concepts/named.hpp"
///////////////////////////////////////////////////////////////////////////////
/// 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 <concepts>
#include <tuple>
namespace cruft::concepts {
/// Tests if the type has all typedefs required for use with
/// std::iterator_traits.
@ -301,10 +45,10 @@ namespace cruft::concepts {
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>;
{ t * t } -> std::convertible_to<T>;
{ t / t } -> std::convertible_to<T>;
{ t - t } -> std::convertible_to<T>;
{ t + t } -> std::convertible_to<T>;
};
@ -312,10 +56,10 @@ namespace cruft::concepts {
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;
{ std::begin (t) } -> named::legacy_iterator;
{ std::end (t) } -> named::legacy_iterator;
{ std::cbegin (t) } -> named::legacy_iterator;
{ std::cend (t) } -> named::legacy_iterator;
};
@ -326,7 +70,7 @@ namespace cruft::concepts {
// 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_size<T>::value } -> std::convertible_to<std::size_t>;
{ std::tuple_cat (a, b) };
};
}
}

94
concepts/named.hpp Normal file
View File

@ -0,0 +1,94 @@
#pragma once
#include <iterator>
#include <concepts>
#include <utility>
///////////////////////////////////////////////////////////////////////////////
// C++ named requirements
namespace cruft::concepts::named {
/// Corresponds to the "Container" named requirement.
template <class T>
concept container =
std::default_initializable<T> and
std::copy_constructible<T> and
std::move_constructible<T> and
std::destructible<T> and
std::equality_comparable<T> and
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 } -> std::same_as<T&>;
{ a = std::move (b) } -> std::same_as<T&>;
{ a.begin () } -> std::same_as<typename T::iterator>;
{ a.end () } -> std::same_as<typename T::iterator>;
{ a.cbegin () } -> std::same_as<typename T::const_iterator>;
{ a.cend () } -> std::same_as<typename T::const_iterator>;
{ a.swap (b) } -> std::same_as<void>;
{ std::swap (a, b) } -> std::same_as<void>;
{ a.size () } -> std::same_as<typename T::size_type>;
{ a.max_size () } -> std::same_as<typename T::size_type>;
{ a.empty () } -> std::same_as<bool>;
};
template <typename T>
concept move_assignable =
requires (T a, T b)
{
{ a = std::move (b) } -> std::same_as<T&>;
};
template <typename T>
concept copy_assignable =
move_assignable<T> &&
requires (T a, T b)
{
{ a = b } -> std::same_as<T&>;
};
template <typename T>
concept legacy_iterator =
std::copy_constructible<T> &&
copy_assignable<T> &&
std::destructible<T> &&
std::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 } -> std::same_as<T&>;
};
template <typename T>
concept legacy_input_iterator =
legacy_iterator<T> &&
std::equality_comparable<T> &&
requires (T a, T b)
{
typename std::iterator_traits<T>::reference;
typename std::iterator_traits<T>::value_type;
{ a != b } -> std::same_as<bool>;
{ ++a } -> std::same_as<T&>;
{ a++ };
};
}

23
concepts/string.hpp Normal file
View File

@ -0,0 +1,23 @@
/*
* 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 2022, Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include <concepts>
#include <string>
#include <string_view>
namespace cruft::concepts {
/// A type that behaves like a string. ie, a sequence of characters
template <typename T>
concept stringy =
std::same_as<T, std::string> or
std::same_as<T, std::string_view> or
std::same_as<T, char const*> or
std::same_as<T, char *>;
}

23
concepts/traits.hpp Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <type_traits>
///////////////////////////////////////////////////////////////////////////////
// Trivial wrappers around traits
namespace cruft::concepts::traits {
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>;
}

View File

@ -12,7 +12,10 @@
// it triggers a circular dependency; debug -> format -> maths -> debug
// instead, just use cassert
#include "concepts/traits.hpp"
#include "concepts/named.hpp"
#include "concepts.hpp"
#include "types/traits.hpp"
#include "float.hpp"
@ -36,7 +39,7 @@
///////////////////////////////////////////////////////////////////////////////
namespace cruft {
///////////////////////////////////////////////////////////////////////////
template <concepts::arithmetic T>
template <concepts::traits::arithmetic T>
constexpr T
abs [[gnu::const]] (T t)
{
@ -151,7 +154,7 @@ namespace cruft {
template <
typename BaseT,
concepts::integral ExponentT
std::integral ExponentT
>
constexpr BaseT
pow [[gnu::const]] (BaseT base, ExponentT exponent)
@ -174,7 +177,7 @@ namespace cruft {
//-------------------------------------------------------------------------
template <concepts::integral T>
template <std::integral T>
constexpr bool
is_pow2 [[gnu::const]] (T value)
{
@ -188,7 +191,7 @@ namespace cruft {
///
/// `val` must be strictly greater than zero, otherwise the results are
/// undefined.
template <concepts::integral T>
template <std::integral T>
constexpr T
log2 (T val)
{
@ -219,7 +222,7 @@ namespace cruft {
/// with runtime performance given the simplistic construction.
///
/// It's useful for sizing temporary arrays.
template <concepts::integral T>
template <std::integral T>
consteval T
ilog (T val, T base)
{
@ -232,7 +235,7 @@ namespace cruft {
///////////////////////////////////////////////////////////////////////////////
/// round T up to the nearest multiple of U
template <concepts::integral T, concepts::integral U>
template <std::integral T, std::integral U>
inline
std::common_type_t<T, U>
round_up (T value, U size)
@ -247,7 +250,7 @@ namespace cruft {
///----------------------------------------------------------------------------
/// round T up to the nearest power-of-2
template <concepts::integral T>
template <std::integral T>
constexpr auto
round_pow2 (T value)
{
@ -265,8 +268,8 @@ namespace cruft {
///----------------------------------------------------------------------------
/// round T up to the nearest multiple of U and return the quotient.
template <
concepts::integral T,
concepts::integral U
std::integral T,
std::integral U
>
constexpr auto
divup (T const a, U const b)
@ -277,7 +280,7 @@ namespace cruft {
///////////////////////////////////////////////////////////////////////////////
// Properties
template <concepts::integral T>
template <std::integral T>
constexpr bool
is_integer (T)
{
@ -285,7 +288,7 @@ namespace cruft {
}
template <concepts::floating_point T>
template <std::floating_point T>
constexpr bool
is_integer (T t)
{
@ -295,7 +298,7 @@ namespace cruft {
//-------------------------------------------------------------------------
template <concepts::integral NumericT>
template <std::integral NumericT>
constexpr auto
digits10 (NumericT v) noexcept
{
@ -325,7 +328,7 @@ namespace cruft {
}
template <concepts::integral ValueT, concepts::integral BaseT>
template <std::integral ValueT, std::integral BaseT>
constexpr int
digits (ValueT value, BaseT base) noexcept
{
@ -344,7 +347,7 @@ namespace cruft {
///----------------------------------------------------------------------------
/// return positive or negative unit value corresponding to the input.
template <concepts::signed_integral T>
template <std::signed_integral T>
constexpr T
sign (T t)
{
@ -355,7 +358,7 @@ namespace cruft {
/// return positive or negative unit value corresponding to the input.
/// guaranteed to give correct results for signed zeroes, use another
/// method if extreme speed is important.
template <concepts::floating_point T>
template <std::floating_point T>
constexpr T
sign (T t)
{
@ -386,7 +389,7 @@ namespace cruft {
// Modulus/etc
// namespaced wrapper for `man 3 fmod`
template <concepts::floating_point T>
template <std::floating_point T>
constexpr T
mod (T x, T y)
{
@ -394,7 +397,7 @@ namespace cruft {
}
template <concepts::integral T>
template <std::integral T>
constexpr T
mod (T x, T y)
{
@ -402,7 +405,7 @@ namespace cruft {
}
template <concepts::floating_point ValueT>
template <std::floating_point ValueT>
ValueT
frac (ValueT val)
{
@ -515,8 +518,8 @@ namespace cruft {
template <typename InputT>
requires
concepts::legacy_input_iterator<InputT> &&
concepts::floating_point<typename std::iterator_traits<InputT>::value_type>
concepts::named::legacy_input_iterator<InputT> &&
std::floating_point<typename std::iterator_traits<InputT>::value_type>
typename std::iterator_traits<InputT>::value_type
sum (InputT first, InputT last)
{
@ -545,8 +548,8 @@ namespace cruft {
//-------------------------------------------------------------------------
template <typename InputT>
requires
concepts::legacy_input_iterator<InputT> &&
concepts::integral<typename std::iterator_traits<InputT>::value_type>
concepts::named::legacy_input_iterator<InputT> &&
std::integral<typename std::iterator_traits<InputT>::value_type>
typename std::iterator_traits<InputT>::value_type
sum (InputT first, InputT last)
{
@ -575,7 +578,7 @@ namespace cruft {
/// parameter packs.
///
/// eg, `max (sizeof (T)...)` will otherwise fail with a single type.
template <concepts::integral ValueT>
template <std::integral ValueT>
constexpr decltype(auto)
max (ValueT &&val)
{
@ -598,7 +601,7 @@ namespace cruft {
//-------------------------------------------------------------------------
template <concepts::container ContainerT>
template <concepts::named::container ContainerT>
typename ContainerT::value_type const&
max (ContainerT const &vals)
{
@ -606,13 +609,13 @@ namespace cruft {
}
template <concepts::container ValueT>
template <concepts::named::container ValueT>
typename ValueT::value_type &
max (ValueT &&) = delete;
//-------------------------------------------------------------------------
template <concepts::container ContainerT>
template <concepts::named::container ContainerT>
typename ContainerT::value_type const&
min (ContainerT const &vals)
{
@ -620,7 +623,7 @@ namespace cruft {
}
template <concepts::container ContainerT>
template <concepts::named::container ContainerT>
typename ContainerT::value_type&
min (ContainerT &&) = delete;
@ -643,9 +646,9 @@ namespace cruft {
// min/max clamping
template <
concepts::scalar T,
concepts::scalar U,
concepts::scalar V
concepts::traits::scalar T,
concepts::traits::scalar U,
concepts::traits::scalar V
>
constexpr std::common_type_t<T,U,V>
clamp (T const val, U const lo, V const hi)
@ -697,8 +700,8 @@ namespace cruft {
// uint -> float
template <
concepts::unsigned_integral T,
concepts::floating_point U
std::unsigned_integral T,
std::floating_point U
>
constexpr U
renormalise (T t)
@ -710,8 +713,8 @@ namespace cruft {
//-------------------------------------------------------------------------
// float -> uint
template <
concepts::floating_point T,
concepts::unsigned_integral U
std::floating_point T,
std::unsigned_integral U
>
constexpr U
renormalise (T t)
@ -745,8 +748,8 @@ namespace cruft {
// ambiguous overloads
template <typename T, typename U>
requires
concepts::floating_point<T> &&
concepts::floating_point<U> &&
std::floating_point<T> &&
std::floating_point<U> &&
(!std::is_same_v<T, U>)
constexpr U
renormalise (T t)
@ -759,8 +762,8 @@ namespace cruft {
// hi_uint -> lo_uint
template <typename T, typename U>
requires
concepts::unsigned_integral<T> &&
concepts::unsigned_integral<U> &&
std::unsigned_integral<T> &&
std::unsigned_integral<U> &&
(sizeof (T) > sizeof (U))
constexpr U
renormalise (T t)
@ -781,8 +784,8 @@ namespace cruft {
typename DstT
>
requires
concepts::unsigned_integral<SrcT> &&
concepts::unsigned_integral<DstT> &&
std::unsigned_integral<SrcT> &&
std::unsigned_integral<DstT> &&
(sizeof (SrcT) < sizeof (DstT))
constexpr DstT
renormalise (SrcT src)
@ -834,7 +837,7 @@ namespace cruft {
// anything-to-sint
template <typename T, typename U>
requires
concepts::signed_integral<U> &&
std::signed_integral<U> &&
(!std::is_same<T,U>::value)
constexpr U
renormalise (T t)
@ -851,7 +854,7 @@ namespace cruft {
// sint-to-anything
template <typename T, typename U>
requires
concepts::signed_integral<T> &&
std::signed_integral<T> &&
(!std::is_same<T,U>::value)
constexpr U
renormalise (T sint)

View File

@ -1,12 +1,12 @@
#include <cruft/util/tap.hpp>
#include <cruft/util/concepts.hpp>
#include <cruft/util/concepts/named.hpp>
int main ()
{
cruft::TAP::logger tap;
tap.expect (cruft::concepts::container<std::vector<int>>, "vector is a container");
tap.expect (!cruft::concepts::container<int>, "int is not a container");
tap.expect (cruft::concepts::named::container<std::vector<int>>, "vector is a container");
tap.expect (!cruft::concepts::named::container<int>, "int is not a container");
tap.expect (cruft::concepts::tuple<std::tuple<>>, "tuple<> is a tuple");
tap.expect (cruft::concepts::tuple<std::tuple<int>>, "tuple<int> is a tuple");

View File

@ -14,6 +14,7 @@
#include "../maths.hpp"
#include "../tuple/type.hpp"
#include <concepts>
#include <tuple>
@ -54,7 +55,7 @@ namespace cruft {
//---------------------------------------------------------------------
template <typename InitialT>
requires (cruft::concepts::same_as<std::remove_cvref_t<InitialT>, ValueT> || ...)
requires (std::same_as<std::remove_cvref_t<InitialT>, ValueT> || ...)
tagged (InitialT &&initial)
{
set<InitialT> (std::forward<InitialT> (initial));