based

Opinionated utility library
git clone git://git.dimitrijedobrota.com/based.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit c4faa23f38f69f7100c2444637aed7f312db5704
parent 523b08ac6fe91c3418e5613ccaee506a7c4aa781
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Wed, 30 Apr 2025 13:39:52 +0200

Rework function concepts to enforce return type

- reduce doesn't work

Diffstat:
M CMakeLists.txt | + -
M example/type_traits.cpp | ++++++++++++++++++++++++++++++++ -------------------------------------
M include/based/algorithm.hpp | +++++++++++++++++++ -----------
M include/based/type_traits.hpp | +++++++++++++++++++++++++++++++ ---------------------------------------------------
M test/source/reduce_test.cpp | ++

5 files changed, 94 insertions(+), 115 deletions(-)


diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)


project(
based
VERSION 0.1.0
VERSION 0.1.1
DESCRIPTION "Opinionated utility library"
HOMEPAGE_URL "https://git.dimitrijedobrota.com/based.git"
LANGUAGES CXX

diff --git a/ example/type_traits.cpp b/ example/type_traits.cpp

@@ -49,24 +49,20 @@ T sub(T val1, U val2)


int main()
{
static_assert(based::arity_v<no_return> == 0);
static_assert(based::Procedure<no_return>);
static_assert(!based::RegularProcedure<no_return>);
static_assert(!based::FunctionalProcedure<no_return>);
static_assert(based::Procedure<no_return, void>);
static_assert(!based::RegularProcedure<no_return, void>);
static_assert(!based::FunctionalProcedure<no_return, void>);

using id = identity<double>;
using ii = identity<irregular>;

static_assert(std::same_as<based::codomain_t<id, double>, double>);
static_assert(based::arity_v<id, double> == 1);

static_assert(based::Procedure<id, double>);
static_assert(based::Procedure<id, double, double>);
static_assert(!based::Procedure<ii, irregular>);

static_assert(based::RegularProcedure<id, double>);
static_assert(based::RegularProcedure<id, double, double>);
static_assert(!based::RegularProcedure<ii, irregular>);

static_assert(based::FunctionalProcedure<id, double>);
static_assert(based::FunctionalProcedure<id, double, double>);
static_assert(!based::FunctionalProcedure<ii, irregular>);

using ad = add<double, double>;

@@ -74,58 +70,57 @@ int main()

using aid = add<irregular, double>;
using adi = add<double, irregular>;

static_assert(std::same_as<based::codomain_t<ad, double, double>, double>);
static_assert(based::arity_v<ad, double, double> == 2);

static_assert(based::Procedure<ad, double, double>);
static_assert(based::Procedure<ai, irregular, irregular>);
static_assert(based::Procedure<aid, irregular, double>);
static_assert(based::Procedure<adi, double, irregular>);
static_assert(based::Procedure<ad, double, double, double>);
static_assert(based::Procedure<ai, irregular, irregular, irregular>);
static_assert(based::Procedure<aid, irregular, irregular, double>);
static_assert(based::Procedure<adi, double, double, irregular>);

static_assert(based::RegularProcedure<ad, double, double>);
static_assert(!based::RegularProcedure<ai, irregular, irregular>);
static_assert(!based::RegularProcedure<aid, irregular, double>);
static_assert(!based::RegularProcedure<adi, double, irregular>);
static_assert(based::RegularProcedure<ad, double, double, double>);
static_assert(!based::RegularProcedure<ai, irregular, irregular, irregular>);
static_assert(!based::RegularProcedure<aid, irregular, irregular, double>);
static_assert(!based::RegularProcedure<adi, double, double, irregular>);

static_assert(based::FunctionalProcedure<ad, double, double>);
static_assert(!based::FunctionalProcedure<ai, irregular, irregular>);
static_assert(!based::FunctionalProcedure<aid, irregular, double>);
static_assert(!based::FunctionalProcedure<adi, double, irregular>);
static_assert(based::FunctionalProcedure<ad, double, double, double>);
static_assert(!based::
FunctionalProcedure<ai, irregular, irregular, irregular>);
static_assert(!based::FunctionalProcedure<aid, irregular, irregular, double>);
static_assert(!based::FunctionalProcedure<adi, double, double, irregular>);

using md = mutate<double>;

static_assert(based::Procedure<md, double*>);
static_assert(based::RegularProcedure<md, double*>);
static_assert(!based::FunctionalProcedure<md>);
static_assert(based::Procedure<md, double, double*>);
static_assert(based::RegularProcedure<md, double, double*>);
static_assert(!based::FunctionalProcedure<md, double, double*>);

static_assert(based::RegularProcedure<
decltype(sub<double, double>),
double,
double,
double>);

static const auto func1 = [](double /* a */)
{
return 1;
};
static_assert(based::Procedure<decltype(func1), double>);
static_assert(based::RegularProcedure<decltype(func1), double>);
static_assert(based::FunctionalProcedure<decltype(func1), double>);
static_assert(based::Procedure<decltype(func1), int, double>);
static_assert(based::RegularProcedure<decltype(func1), int, double>);
static_assert(based::FunctionalProcedure<decltype(func1), int, double>);

static const auto func2 = [](irregular /* a */)
{
return 1;
};
static_assert(!based::Procedure<decltype(func2), irregular>);
static_assert(!based::RegularProcedure<decltype(func2), irregular>);
static_assert(!based::FunctionalProcedure<decltype(func2), irregular>);
static_assert(!based::Procedure<decltype(func2), int, irregular>);
static_assert(!based::RegularProcedure<decltype(func2), int, irregular>);
static_assert(!based::FunctionalProcedure<decltype(func2), int, irregular>);

static const auto func3 = [](const irregular& /* a */)
{
return 1;
};
static_assert(based::Procedure<decltype(func3), irregular>);
static_assert(!based::RegularProcedure<decltype(func3), irregular>);
static_assert(!based::FunctionalProcedure<decltype(func3), irregular>);
static_assert(based::Procedure<decltype(func3), int, irregular>);
static_assert(!based::RegularProcedure<decltype(func3), int, irregular>);
static_assert(!based::FunctionalProcedure<decltype(func3), int, irregular>);

return 0;
}

diff --git a/ include/based/algorithm.hpp b/ include/based/algorithm.hpp

@@ -16,11 +16,7 @@ namespace detail

/* ----- Min and Max ----- */

template<typename P, typename Arg>
concept NoninputRelation = requires {
requires(RegularProcedure<P, Arg, Arg>);
requires(std::same_as<bool, codomain_t<P, Arg, Arg>>);
requires(arity_v<P, Arg, Arg> == 2);
};
concept NoninputRelation = RegularProcedure<P, bool, Arg, Arg>;

} // namespace detail

@@ -167,7 +163,7 @@ std::pair<I, I> minmax_element(I first, I last)

return based::minmax_element(first, last, std::less<iter_value_t<I>>());
}

template<ReadableIterator I, IterUnaryProcedure<I> Proc>
template<ReadableIterator I, IterUnaryProcedure<void, I> Proc>
Proc for_each(I first, I last, Proc proc)
{
// Precondition: readable_bounded_range(first, last);

@@ -326,7 +322,11 @@ iter_dist_t<I> count_if_not(I first, I last, Pred pred)

return count_if_not(first, last, pred, iter_dist_t<I> {0});
}

template<Iterator I, UnaryFunction<I> F, BinaryOperation<codomain_t<F, I>> Op>
template<
typename Ret,
Iterator I,
UnaryFunction<Ret, I> F,
BinaryOperation<Ret> Op>
auto reduce_nonempty(I first, I last, Op opr, F fun)
{
assert(first != last);

@@ -342,7 +342,11 @@ auto reduce_nonempty(I first, I last, Op opr, F fun)

return res;
}

template<Iterator I, UnaryFunction<I> F, BinaryOperation<codomain_t<F, I>> Op>
template<
typename Ret,
Iterator I,
UnaryFunction<Ret, I> F,
BinaryOperation<Ret> Op>
auto reduce(
I first,
I last,

@@ -359,7 +363,11 @@ auto reduce(

return reduce_nonempty(first, last, opr, fun);
}

template<Iterator I, UnaryFunction<I> F, BinaryOperation<codomain_t<F, I>> Op>
template<
typename Ret,
Iterator I,
UnaryFunction<Ret, I> F,
BinaryOperation<Ret> Op>
auto reduce_nonzero(
I first,
I last,

@@ -370,7 +378,7 @@ auto reduce_nonzero(

{
// Precondition: bounded_range(first, last)
// Precondition: partially_associative(opr)
codomain_t<F, I> res;
Ret res;
do {
if (first == last) {
return zero;

@@ -449,7 +457,7 @@ bool increasing_range(I first, I last, Rel rel)


/* ----- Counted Range Algorithms ----- */

template<ReadableIterator I, IterUnaryProcedure<I> Proc>
template<ReadableIterator I, IterUnaryProcedure<void, I> Proc>
auto for_each_n(I first, iter_dist_t<I> size, Proc proc)
{
// Precondition: readable_weak_range(first, size);

diff --git a/ include/based/type_traits.hpp b/ include/based/type_traits.hpp

@@ -139,6 +139,10 @@ struct is_homogeneous_domain<Head, Args...> : std::true_type


} // namespace detail

template<std::size_t idx, typename... Args>
requires(idx < sizeof...(Args))
using elem_t = std::tuple_element_t<idx, std::tuple<Args...>>;

template<typename... Args>
concept RegularDomain =
requires { requires(Regular<std::remove_cvref_t<Args>> && ...); };

@@ -147,121 +151,91 @@ template<typename... Args>

concept InputDomain = requires { requires(Input<Args> && ...); };

template<typename... Args>
concept HomogenousDomain = detail::is_homogeneous_domain<Args...>::value;
concept HomogeneousDomain = detail::is_homogeneous_domain<Args...>::value;

template<typename T>
using distance_t = std::uint64_t;

template<typename P, typename... Args>
concept Procedure = std::invocable<P, Args...>;

template<typename P, typename... Args>
requires Procedure<P, Args...>
inline constexpr auto arity_v = std::tuple_size<std::tuple<Args...>>::value;

template<typename P, std::size_t idx, typename... Args>
requires Procedure<P, Args...> && requires { idx < arity_v<Args...>; }
using domain_elem_t =
std::decay_t<std::tuple_element_t<idx, std::tuple<Args...>>>;

template<typename P, typename... Args>
requires Procedure<P, Args...>
using codomain_t = std::invoke_result_t<P, Args...>;

template<typename P, typename Arg>
concept UnaryProcedure = requires {
requires(Procedure<P, Arg>);
requires(arity_v<P, Arg> == 1);
template<typename P, typename Ret, typename... Args>
concept Procedure = requires {
requires(std::invocable<P, Args...>);
requires(std::same_as<void, Ret> || std::same_as<Ret, std::invoke_result_t<P, Args...>>);
};

template<typename P, typename... Args>
template<typename P, typename Ret, typename Arg>
concept UnaryProcedure = Procedure<P, Ret, Arg>;

template<typename P, typename Ret, typename... Args>
concept RegularProcedure = requires {
requires(Procedure<P, Args...>);
requires(Procedure<P, Ret, Args...>);
requires(RegularDomain<Args...>);
requires(Regular<codomain_t<P, Args...>>);
requires(Regular<Ret>);
};

template<typename P, typename... Args>
template<typename P, typename Ret, typename... Args>
concept FunctionalProcedure = requires {
requires(RegularProcedure<P, Args...>);
requires(RegularProcedure<P, Ret, Args...>);
requires(InputDomain<Args...>);
};

template<typename P, typename Arg>
template<typename P, typename Ret, typename Arg>
concept UnaryFunction = requires {
requires(FunctionalProcedure<P, Arg>);
requires(UnaryProcedure<P, Arg>);
requires(FunctionalProcedure<P, Ret, Arg>);
requires(UnaryProcedure<P, Ret, Arg>);
};

template<typename P, typename... Args>
template<typename P, typename Ret, typename... Args>
concept HomogeneousFunction = requires {
requires(FunctionalProcedure<P, Args...>);
requires(arity_v<P, Args...> > 0);
requires(HomogenousDomain<Args...>);
requires(FunctionalProcedure<P, Ret, Args...>);
requires(sizeof...(Args) > 0);
requires(HomogeneousDomain<Args...>);
};

template<typename P, typename... Args>
concept Predicate = requires {
requires(FunctionalProcedure<P, Args...>);
requires(std::same_as<bool, codomain_t<P, Args...>>);
};
concept Predicate = FunctionalProcedure<P, bool, Args...>;

template<typename P, typename... Args>
concept HomogeneousPredicate = requires {
requires(Predicate<P, Args...>);
requires(HomogeneousFunction<P, Args...>);
requires(HomogeneousFunction<P, bool, Args...>);
};

template<typename P, typename Arg>
concept UnaryPredicate = requires {
requires(Predicate<P, Arg>);
requires(UnaryFunction<P, Arg>);
requires(UnaryFunction<P, bool, Arg>);
};

template<typename P, typename... Args>
concept Operation = requires {
requires(HomogeneousFunction<P, Args...>);
requires(BareSameAs<
codomain_t<P, Args...>,
std::tuple_element_t<0, std::tuple<Args...>>>);
};
concept Operation = HomogeneousFunction<P, elem_t<0, Args...>, Args...>;

template<typename P, typename Arg>
template<typename P, typename Ret, typename Arg>
concept Transformation = requires {
requires(Operation<P, Arg>);
requires(UnaryFunction<P, Arg>);
requires(Operation<P, Ret, Arg>);
requires(UnaryFunction<P, Ret, Arg>);
};

template<typename P, typename Arg>
concept BinaryOperation = requires {
requires(Operation<P, Arg, Arg>);
requires(arity_v<P, Arg, Arg> == 2);
};
concept BinaryOperation = Operation<P, Arg, Arg>;

template<typename P, typename Arg>
concept AssociativeBinaryOperation = requires {
requires(Operation<P, Arg, Arg>);
requires(arity_v<P, Arg, Arg> == 2);
};
concept AssociativeBinaryOperation = Operation<P, Arg, Arg>;

template<typename P, typename Arg>
concept Relation = requires {
requires(HomogeneousPredicate<P, Arg, Arg>);
requires(arity_v<P, Arg, Arg> == 2);
};
concept Relation = HomogeneousPredicate<P, Arg, Arg>;

/* ----- Iterator variants ----- */

template<typename P, typename I>
template<typename P, typename Ret, typename I>
concept IterUnaryProcedure = requires {
requires(Iterator<I>);
requires(UnaryProcedure<P, iter_value_t<I>>);
requires(UnaryProcedure<P, Ret, iter_value_t<I>>);
};

template<typename P, typename I>
template<typename P, typename Ret, typename I>
concept IterUnaryFunction = requires {
requires(Iterator<I>);
requires(UnaryFunction<P, iter_value_t<I>>);
requires(UnaryFunction<P, Ret, iter_value_t<I>>);
};

template<typename P, typename... I>

@@ -276,13 +250,13 @@ concept IterUnaryPredicate = requires {

requires(UnaryPredicate<P, iter_value_t<I>>);
};

template<typename P, typename... I>
template<typename P, typename Ret, typename... I>
concept IterOperation = requires {
requires(Iterator<I> && ...);
requires(Operation<P, iter_value_t<I>...>);
};

template<typename P, typename I>
template<typename P, typename Ret, typename I>
concept IterBinaryOperation = requires {
requires(Iterator<I>);
requires(BinaryOperation<P, iter_value_t<I>>);

diff --git a/ test/source/reduce_test.cpp b/ test/source/reduce_test.cpp

@@ -15,6 +15,7 @@ struct fun

auto operator()(based::Iterator auto itr) { return *itr; }
};

/*
TEST_CASE("reduce(empty)", "[algorithm/reduce]")
{
const std::array<int, 0> arr = {};

@@ -54,3 +55,4 @@ TEST_CASE("reduce_nonzero(sequence)", "[algorithm/reduce_nonzero]")


REQUIRE(count == 15);
}
*/