Add concept invocable and further missing concepts to concepts.h

This commit is contained in:
Roland Reichwein 2026-04-25 12:38:23 +02:00
parent 3e4d41ca57
commit ae7c8c870a
4 changed files with 426 additions and 0 deletions

View File

@ -33,6 +33,7 @@ SOFTWARE.
#include "platform.h"
#include "invoke.h"
#include "type_traits.h"
#include "utility.h"
@ -54,12 +55,31 @@ namespace etl
using std::assignable_from;
using std::common_reference_with;
using std::common_with;
using std::constructible_from;
using std::convertible_to;
using std::copy_constructible;
using std::copyable;
using std::default_initializable;
using std::derived_from;
using std::destructible;
using std::equality_comparable;
using std::equivalence_relation;
using std::floating_point;
using std::integral;
using std::invocable;
using std::movable;
using std::move_constructible;
using std::predicate;
using std::regular;
using std::regular_invocable;
using std::relation;
using std::same_as;
using std::semiregular;
using std::signed_integral;
using std::strict_weak_order;
using std::swappable;
using std::swappable_with;
using std::totally_ordered;
using std::unsigned_integral;
#else // not ETL_USING_STL
@ -118,6 +138,112 @@ namespace etl
{ lhs = etl::forward<RHS>(rhs) } -> etl::same_as<LHS>;
};
//***************************************************************************
template <typename F, typename... Args>
concept invocable = etl::is_invocable_v<F, Args...>;
//***************************************************************************
template <typename F, typename... Args>
concept regular_invocable = etl::invocable<F, Args...>;
//***************************************************************************
template <typename T>
concept destructible = requires(T& t) {
{ t.~T() } noexcept;
};
//***************************************************************************
template <typename T, typename... Args>
concept constructible_from = etl::destructible<T> && etl::is_constructible_v<T, Args...>;
//***************************************************************************
template <typename T>
concept default_initializable = etl::constructible_from<T> && requires {
T{};
::new T;
};
//***************************************************************************
template <typename T>
concept move_constructible = etl::constructible_from<T, T> && etl::convertible_to<T, T>;
//***************************************************************************
template <typename T>
concept copy_constructible =
etl::move_constructible<T> && etl::constructible_from<T, T&> && etl::convertible_to<T&, T> && etl::constructible_from<T, const T&>
&& etl::convertible_to<const T&, T> && etl::constructible_from<T, const T> && etl::convertible_to<const T, T>;
//***************************************************************************
namespace private_concepts
{
template <typename T>
concept boolean_testable = etl::convertible_to<T, bool> && requires(T&& t) {
{ !etl::forward<T>(t) } -> etl::convertible_to<bool>;
};
} // namespace private_concepts
//***************************************************************************
template <typename T>
concept equality_comparable = requires(const etl::remove_reference_t<T>& a, const etl::remove_reference_t<T>& b) {
{ a == b } -> private_concepts::boolean_testable;
{ a != b } -> private_concepts::boolean_testable;
};
//***************************************************************************
template <typename T>
concept totally_ordered = etl::equality_comparable<T> && requires(const etl::remove_reference_t<T>& a, const etl::remove_reference_t<T>& b) {
{ a < b } -> private_concepts::boolean_testable;
{ a > b } -> private_concepts::boolean_testable;
{ a <= b } -> private_concepts::boolean_testable;
{ a >= b } -> private_concepts::boolean_testable;
};
//***************************************************************************
template <typename T>
concept swappable = requires(T& a, T& b) { etl::swap(a, b); };
//***************************************************************************
template <typename T, typename U>
concept swappable_with = etl::common_reference_with<etl::remove_reference_t<T>&, etl::remove_reference_t<U>&> && requires(T&& t, U&& u) {
etl::swap(etl::forward<T>(t), etl::forward<T>(t));
etl::swap(etl::forward<U>(u), etl::forward<U>(u));
etl::swap(etl::forward<T>(t), etl::forward<U>(u));
etl::swap(etl::forward<U>(u), etl::forward<T>(t));
};
//***************************************************************************
template <typename T>
concept movable = etl::is_object_v<T> && etl::move_constructible<T> && etl::assignable_from<T&, T> && etl::swappable<T>;
//***************************************************************************
template <typename T>
concept copyable = etl::copy_constructible<T> && etl::movable<T> && etl::assignable_from<T&, T&> && etl::assignable_from<T&, const T&>
&& etl::assignable_from<T&, const T>;
//***************************************************************************
template <typename T>
concept semiregular = etl::copyable<T> && etl::default_initializable<T>;
//***************************************************************************
template <typename T>
concept regular = etl::semiregular<T> && etl::equality_comparable<T>;
//***************************************************************************
template <typename F, typename... Args>
concept predicate = etl::regular_invocable<F, Args...> && private_concepts::boolean_testable<etl::invoke_result_t<F, Args...> >;
//***************************************************************************
template <typename R, typename T, typename U>
concept relation = etl::predicate<R, T, T> && etl::predicate<R, U, U> && etl::predicate<R, T, U> && etl::predicate<R, U, T>;
//***************************************************************************
template <typename R, typename T, typename U>
concept equivalence_relation = etl::relation<R, T, U>;
//***************************************************************************
template <typename R, typename T, typename U>
concept strict_weak_order = etl::relation<R, T, U>;
#endif
} // namespace etl
#endif

View File

@ -649,6 +649,18 @@ namespace etl
struct is_void<void> : true_type
{
};
template <>
struct is_void<const void> : true_type
{
};
template <>
struct is_void<volatile void> : true_type
{
};
template <>
struct is_void<const volatile void> : true_type
{
};
#if ETL_USING_CPP17
template <typename T>
@ -3553,6 +3565,19 @@ namespace etl
template <typename T>
inline constexpr bool is_function_v = etl::is_function<T>::value;
#endif
//***************************************************************************
/// is_object
//***************************************************************************
template <typename T>
struct is_object : etl::bool_constant<!etl::is_function<T>::value && !etl::is_reference<T>::value && !etl::is_void<T>::value>
{
};
#if ETL_USING_CPP17
template <typename T>
inline constexpr bool is_object_v = etl::is_object<T>::value;
#endif
#endif
#if ETL_USING_CPP11

View File

@ -29,6 +29,7 @@ SOFTWARE.
#include "unit_test_framework.h"
#include <atomic>
#include <functional>
#include <string>
#if ETL_USING_CPP20
@ -52,6 +53,65 @@ namespace
{
};
struct NotDestructible
{
~NotDestructible() = delete;
};
struct NotDefaultConstructible
{
NotDefaultConstructible(int) {}
};
struct NotCopyable
{
NotCopyable() = default;
NotCopyable(const NotCopyable&) = delete;
NotCopyable(NotCopyable&&) = default;
NotCopyable& operator=(const NotCopyable&) = delete;
NotCopyable& operator=(NotCopyable&&) = default;
};
struct NotMovable
{
NotMovable() = default;
NotMovable(const NotMovable&) = delete;
NotMovable(NotMovable&&) = delete;
NotMovable& operator=(const NotMovable&) = delete;
NotMovable& operator=(NotMovable&&) = delete;
};
struct NotEqualityComparable
{
};
struct EqualityComparableType
{
bool operator==(const EqualityComparableType&) const = default;
};
struct OrderedType
{
int value;
auto operator<=>(const OrderedType&) const = default;
};
struct BoolPredicate
{
bool operator()(int) const
{
return true;
}
};
struct IntRelation
{
bool operator()(int, int) const
{
return true;
}
};
SUITE(test_concepts)
{
//*************************************************************************
@ -140,6 +200,183 @@ namespace
static_assert(etl::assignable_from<std::string, std::string> == false);
static_assert(etl::assignable_from<std::atomic<int>&, int> == false);
}
//*************************************************************************
TEST(test_invocable)
{
struct Functor
{
void operator()() {}
};
struct FunctorWithArgs
{
int operator()(int, double)
{
return 0;
}
};
static_assert(etl::invocable<void()> == true);
static_assert(etl::invocable<void(int), int> == true);
static_assert(etl::invocable<Functor> == true);
static_assert(etl::invocable<FunctorWithArgs, int, double> == true);
static_assert(etl::invocable<FunctorWithArgs> == false);
static_assert(etl::invocable<int> == false);
}
//*************************************************************************
TEST(test_regular_invocable)
{
struct Functor
{
void operator()() {}
};
static_assert(etl::regular_invocable<void()> == true);
static_assert(etl::regular_invocable<Functor> == true);
static_assert(etl::regular_invocable<int> == false);
}
//*************************************************************************
TEST(test_destructible)
{
static_assert(etl::destructible<int> == true);
static_assert(etl::destructible<std::string> == true);
static_assert(etl::destructible<NotDestructible> == false);
}
//*************************************************************************
TEST(test_constructible_from)
{
static_assert(etl::constructible_from<int> == true);
static_assert(etl::constructible_from<int, int> == true);
static_assert(etl::constructible_from<std::string, const char*> == true);
static_assert(etl::constructible_from<NotDestructible> == false);
}
//*************************************************************************
TEST(test_default_initializable)
{
static_assert(etl::default_initializable<int> == true);
static_assert(etl::default_initializable<std::string> == true);
static_assert(etl::default_initializable<NotDefaultConstructible> == false);
}
//*************************************************************************
TEST(test_move_constructible)
{
static_assert(etl::move_constructible<int> == true);
static_assert(etl::move_constructible<std::string> == true);
static_assert(etl::move_constructible<NotMovable> == false);
}
//*************************************************************************
TEST(test_copy_constructible)
{
static_assert(etl::copy_constructible<int> == true);
static_assert(etl::copy_constructible<std::string> == true);
static_assert(etl::copy_constructible<NotCopyable> == false);
}
//*************************************************************************
TEST(test_equality_comparable)
{
static_assert(etl::equality_comparable<int> == true);
static_assert(etl::equality_comparable<EqualityComparableType> == true);
static_assert(etl::equality_comparable<NotEqualityComparable> == false);
}
//*************************************************************************
TEST(test_totally_ordered)
{
static_assert(etl::totally_ordered<int> == true);
static_assert(etl::totally_ordered<OrderedType> == true);
static_assert(etl::totally_ordered<EqualityComparableType> == false);
}
//*************************************************************************
TEST(test_swappable)
{
static_assert(etl::swappable<int> == true);
static_assert(etl::swappable<std::string> == true);
static_assert(etl::swappable<NotMovable> == false);
}
//*************************************************************************
TEST(test_swappable_with)
{
// Positive cases: same-type lvalue references that are swappable
static_assert(etl::swappable_with<int&, int&> == true);
static_assert(etl::swappable_with<std::string&, std::string&> == true);
// Negative cases: unrelated types (no valid swap overload)
static_assert(etl::swappable_with<int, std::string> == false);
// Negative case: non-movable type cannot be swapped
static_assert(etl::swappable_with<NotMovable&, NotMovable&> == false);
}
//*************************************************************************
TEST(test_movable)
{
static_assert(etl::movable<int> == true);
static_assert(etl::movable<std::string> == true);
static_assert(etl::movable<NotMovable> == false);
}
//*************************************************************************
TEST(test_copyable)
{
static_assert(etl::copyable<int> == true);
static_assert(etl::copyable<std::string> == true);
static_assert(etl::copyable<NotCopyable> == false);
}
//*************************************************************************
TEST(test_semiregular)
{
static_assert(etl::semiregular<int> == true);
static_assert(etl::semiregular<std::string> == true);
static_assert(etl::semiregular<NotDefaultConstructible> == false);
}
//*************************************************************************
TEST(test_regular)
{
static_assert(etl::regular<int> == true);
static_assert(etl::regular<std::string> == true);
static_assert(etl::regular<NotEqualityComparable> == false);
}
//*************************************************************************
TEST(test_predicate)
{
static_assert(etl::predicate<BoolPredicate, int> == true);
static_assert(etl::predicate<bool(int), int> == true);
static_assert(etl::predicate<void(int), int> == false);
}
//*************************************************************************
TEST(test_relation)
{
static_assert(etl::relation<IntRelation, int, int> == true);
static_assert(etl::relation<std::less<int>, int, int> == true);
static_assert(etl::relation<BoolPredicate, int, int> == false);
}
//*************************************************************************
TEST(test_equivalence_relation)
{
static_assert(etl::equivalence_relation<std::equal_to<int>, int, int> == true);
static_assert(etl::equivalence_relation<BoolPredicate, int, int> == false);
}
//*************************************************************************
TEST(test_strict_weak_order)
{
static_assert(etl::strict_weak_order<std::less<int>, int, int> == true);
static_assert(etl::strict_weak_order<BoolPredicate, int, int> == false);
}
}
} // namespace
#endif

View File

@ -1900,5 +1900,43 @@ namespace
CHECK_FALSE((etl::is_function<int MF::*>::value));
CHECK_FALSE((etl::is_function<int (MF::*)(int)>::value)); // pointer, not function
}
//*************************************************************************
TEST(test_is_object)
{
CHECK_TRUE((etl::is_object<int>::value));
CHECK_TRUE((etl::is_object<int*>::value));
CHECK_TRUE((etl::is_object<int[]>::value));
CHECK_TRUE((etl::is_object<int[3]>::value));
CHECK_TRUE((etl::is_object<const int>::value));
CHECK_TRUE((etl::is_object<volatile int>::value));
CHECK_TRUE((etl::is_object<MF>::value));
CHECK_FALSE((etl::is_object<void>::value));
CHECK_FALSE((etl::is_object<const void>::value));
CHECK_FALSE((etl::is_object<volatile void>::value));
CHECK_FALSE((etl::is_object<const volatile void>::value));
CHECK_FALSE((etl::is_object<int&>::value));
CHECK_FALSE((etl::is_object<int&&>::value));
CHECK_FALSE((etl::is_object<decltype(f)>::value));
CHECK_TRUE((etl::is_void<const void>::value));
CHECK_TRUE((etl::is_void<volatile void>::value));
CHECK_TRUE((etl::is_void<const volatile void>::value));
#if ETL_USING_CPP17
CHECK_TRUE((etl::is_object_v<int>));
CHECK_TRUE((etl::is_object_v<int*>));
CHECK_TRUE((etl::is_object_v<MF>));
CHECK_FALSE((etl::is_object_v<void>));
CHECK_FALSE((etl::is_object_v<const void>));
CHECK_FALSE((etl::is_object_v<volatile void>));
CHECK_FALSE((etl::is_object_v<const volatile void>));
CHECK_FALSE((etl::is_object_v<int&>));
CHECK_FALSE((etl::is_object_v<int&&>));
CHECK_FALSE((etl::is_object_v<decltype(f)>));
#endif
}
}
} // namespace