Commit c303b7ab authored by Jan Koniarik's avatar Jan Koniarik
Browse files

First phase of C++20 deployment

parent 650aebc6
Pipeline #80778 passed with stage
in 38 seconds
This diff is collapsed.
......@@ -3,15 +3,8 @@
#pragma once
namespace emlabcpp {
template <typename Container, typename UnaryFunction>
constexpr std::enable_if_t<!is_std_tuple_v<Container>, void> for_each(Container && cont,
UnaryFunction &&f);
namespace emlabcpp::impl {
template <typename Tuple, typename UnaryFunction>
constexpr std::enable_if_t<is_std_tuple_v<Tuple>, void> for_each(Tuple &&t, UnaryFunction &&f);
namespace detail {
template <typename... Args, typename UnaryFunction, std::size_t... Idx>
[[nodiscard]] constexpr std::size_t find_if_impl(const std::tuple<Args...> &t, UnaryFunction &&f,
std::index_sequence<Idx...>) {
......@@ -28,10 +21,6 @@ template <typename... Args, typename UnaryFunction, std::size_t... Idx>
return res;
}
template <typename Tuple, typename UnaryFunction, std::size_t... Idx>
constexpr void for_each_impl(Tuple &&t, UnaryFunction &&f, std::index_sequence<Idx...>) {
(f(std::get<Idx>(std::forward<Tuple>(t))), ...);
}
template <typename T, std::size_t N, typename Container, typename UnaryFunction, std::size_t... Is>
[[nodiscard]] inline std::array<T, N> map_f_to_a_impl(Container &&cont, UnaryFunction &&f,
......@@ -51,5 +40,39 @@ template <typename T, std::size_t N, typename Container, typename UnaryFunction,
// constructor initializer with {} brackets. Otherwise it can be any order ...
return std::array<T, N>{process(Is)...};
}
} // namespace detail
} // namespace emlabcpp
template <typename>
struct map_f_collector;
template <typename T>
requires requires(T a, T::value_type b) {
a.push_back(b);
}
struct map_f_collector<T> {
void collect(T &res, typename T::value_type val) { res.push_back(std::move(val)); }
};
template <typename T>
requires requires(T a, T::value_type b) {
a.insert(b);
}
struct map_f_collector<T> {
void collect(T &res, typename T::value_type val) { res.insert(std::move(val)); }
};
template <typename T, std::size_t N>
struct map_f_collector<std::array<T, N>> {
std::size_t i = 0;
void collect(std::array<T, N> &res, T val) {
res[i] = std::move(val);
i += 1;
}
};
template <typename T>
concept map_f_collectable = requires(T item, T::value_type val) {
map_f_collector<T>{}.collect(item, val);
};
} // namespace emlabcpp::impl
#include <concepts>
#include "emlabcpp/types/base.h"
#pragma once
namespace emlabcpp {
template <typename T>
concept arithmetic_base = requires(T a, T b) {
{ a + b }
->std::convertible_to<T>;
{ a - b }
->std::convertible_to<T>;
{ a / b }
->std::convertible_to<T>;
{ a *b }
->std::convertible_to<T>;
};
template <typename T>
concept arithmetic_assignment = requires(T a, T b) {
{a += b};
{a -= b};
{a /= b};
{a *= b};
};
template <typename T>
concept arithmetic = arithmetic_base<T> &&arithmetic_assignment<T>;
template <typename T>
concept gettable_container = requires(T a) {
{get<0>(a)};
}
|| requires(T a) { {std::get<0>(a)}; };
// so, std::ranges::range is meh because it expects return of begin() being input_output_iterator,
// which has to be def.constructible
template <typename T>
concept range_container = (
requires(T a) { begin(a); } || requires(T a) { std::begin(a); }) &&
(
requires(T a) { end(a); } || requires(T a) { std::end(a); });
template <typename T>
concept container = range_container<T> || gettable_container<T>;
template <typename T>
concept referenceable_container = is_view<T>::value ||
(range_container<T> && !std::is_rvalue_reference_v<T>);
template <typename T>
concept static_sized = requires(T a) {
{ std::tuple_size<std::decay_t<T>>::value }
->std::convertible_to<std::size_t>;
};
template <typename UnaryFunction, typename Container>
concept container_invocable = requires(Container cont, UnaryFunction f) {
f(*cont.begin());
}
|| gettable_container<Container>;
} // namespace emlabcpp
......@@ -10,6 +10,9 @@
namespace emlabcpp {
template <typename T, typename LH, typename RH>
concept either_unique_right = std::same_as<std::decay_t<T>, RH> && !std::same_as<LH, RH>;
/// Either is heterogenous structure that holds one of the two types specified.
/// This is stored as union, so the memory requirement of either is always the size of the bigger
/// element + constant for identifying which on is contained.
......@@ -41,14 +44,8 @@ class either {
either(const left_item &item) noexcept : id_(item::LEFT) { new (&left_) left_item(item); }
template <typename U = RH, typename = std::enable_if_t<!std::is_same_v<LH, U>>>
either(RH &&item) noexcept : id_(item::RIGHT) {
new (&right_) right_item(std::move(item));
}
template <typename U = RH, typename = std::enable_if_t<!std::is_same_v<LH, U>>>
either(const RH &item) noexcept : id_(item::RIGHT) {
new (&right_) right_item(item);
either(either_unique_right<LH, RH> auto &&item) noexcept : id_(item::RIGHT) {
new (&right_) right_item(std::forward<decltype(item)>(item));
}
either(const either &other) noexcept : id_(other.id_) {
......@@ -101,16 +98,14 @@ class either {
return *this;
}
template <typename U = right_item, typename = std::enable_if_t<!std::is_same_v<LH, U>>>
either &operator=(const right_item &other) {
either &operator=(const either_unique_right<LH, RH> auto &other) {
destruct();
id_ = item::RIGHT;
new (&right_) right_item(other);
return *this;
}
template <typename U = right_item, typename = std::enable_if_t<!std::is_same_v<LH, U>>>
either &operator=(RH &&other) {
either &operator=(either_unique_right<LH, RH> auto &&other) {
destruct();
id_ = item::RIGHT;
new (&right_) right_item(std::move(other));
......@@ -119,8 +114,7 @@ class either {
[[nodiscard]] constexpr bool is_left() const { return id_ == item::LEFT; }
template <typename LeftFunction>
auto convert_left(LeftFunction &&left_f) & {
auto convert_left(std::invocable<const left_item &> auto &&left_f) const & {
using return_either = either<decltype(left_f(left_)), right_item>;
if (id_ == item::LEFT) {
......@@ -130,8 +124,7 @@ class either {
return return_either{right_};
}
template <typename LeftFunction>
auto convert_left(LeftFunction &&left_f) && {
auto convert_left(std::invocable<left_item> auto &&left_f) && {
using return_either = either<decltype(left_f(std::move(left_))), right_item>;
if (id_ == item::LEFT) {
......@@ -141,8 +134,7 @@ class either {
return return_either{std::move(right_)};
}
template <typename RightFunction>
auto convert_right(RightFunction &&right_f) & {
auto convert_right(std::invocable<const right_item &> auto &&right_f) const & {
using return_either = either<left_item, decltype(right_f(right_))>;
if (id_ == item::LEFT) {
......@@ -152,8 +144,7 @@ class either {
return return_either{right_f(right_)};
}
template <typename RightFunction>
auto convert_right(RightFunction &&right_f) && {
auto convert_right(std::invocable<right_item> auto &&right_f) && {
using return_either = either<left_item, decltype(right_f(std::move(right_)))>;
if (id_ == item::LEFT) {
......@@ -163,8 +154,8 @@ class either {
return return_either{right_f(std::move(right_))};
}
template <typename LeftFunction, typename RightFunction>
void match(LeftFunction &&left_f, RightFunction &&right_f) & {
void match(std::invocable<left_item &> auto && left_f,
std::invocable<right_item &> auto &&right_f) & {
if (id_ == item::LEFT) {
left_f(left_);
} else {
......@@ -172,8 +163,8 @@ class either {
}
}
template <typename LeftFunction, typename RightFunction>
void match(LeftFunction &&left_f, RightFunction &&right_f) const & {
void match(std::invocable<const left_item &> auto && left_f,
std::invocable<const right_item &> auto &&right_f) const & {
if (id_ == item::LEFT) {
left_f(left_);
} else {
......@@ -181,8 +172,8 @@ class either {
}
}
template <typename LeftFunction, typename RightFunction>
void match(LeftFunction &&left_f, RightFunction &&right_f) && {
void match(std::invocable<left_item> auto && left_f,
std::invocable<right_item> auto &&right_f) && {
if (id_ == item::LEFT) {
left_f(std::move(left_));
} else {
......@@ -199,7 +190,7 @@ class either {
return std::move(right_);
}
template <typename U = left_item, typename K = right_item>
std::enable_if_t<std::is_same_v<U, K>, LH> join() & {
std::enable_if_t<std::is_same_v<U, K>, left_item> join() const & {
if (id_ == item::LEFT) {
return left_;
}
......@@ -208,7 +199,7 @@ class either {
}
template <typename T>
either<left_item, T> construct_right() & {
either<left_item, T> construct_right() const & {
if (id_ == item::LEFT) {
return {left_};
}
......@@ -223,8 +214,7 @@ class either {
return {T{std::move(right_)}};
}
template <typename UnaryFunction>
auto bind_left(UnaryFunction &&left_f) & {
auto bind_left(std::invocable<const left_item &> auto &&left_f) const & {
using return_either =
either<typename decltype(left_f(left_))::left_item, right_item>;
......@@ -234,8 +224,7 @@ class either {
return return_either{right_};
}
template <typename UnaryFunction>
auto bind_left(UnaryFunction &&left_f) && {
auto bind_left(std::invocable<left_item> auto &&left_f) && {
using return_either =
either<typename decltype(left_f(std::move(left_)))::left_item, right_item>;
......@@ -247,7 +236,7 @@ class either {
}
template <typename T>
either<T, right_item> construct_left() & {
either<T, right_item> construct_left() const & {
if (id_ != item::LEFT) {
return {right_};
}
......@@ -262,8 +251,7 @@ class either {
return {T{std::move(left_)}};
}
template <typename UnaryFunction>
auto bind_right(UnaryFunction &&right_f) & {
auto bind_right(std::invocable<const right_item &> auto &&right_f) const & {
using return_either =
either<left_item, typename decltype(right_f(right_))::right_item>;
......@@ -273,8 +261,7 @@ class either {
return return_either{left_};
}
template <typename UnaryFunction>
auto bind_right(UnaryFunction &&right_f) && {
auto bind_right(std::invocable<right_item> auto &&right_f) && {
using return_either =
either<left_item, typename decltype(right_f(std::move(right_)))::right_item>;
......@@ -323,9 +310,9 @@ assemble_optionals(std::optional<Ts> &&...opt) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
template <typename FirstE, typename... Eithers>
inline auto assemble_left_collect_right(FirstE &&first, Eithers &&...others) {
static_assert(are_same_v<typename std::decay_t<Eithers>::right_item...>,
"Right items of Eithers have to be same for collection!");
inline auto assemble_left_collect_right(FirstE &&first, Eithers &&...others) requires(
std::same_as<typename std::decay_t<FirstE>::right_item,
typename std::decay_t<Eithers>::right_item> &&...) {
using right_type = typename std::decay_t<FirstE>::right_item;
constexpr std::size_t either_n = 1 + sizeof...(Eithers);
......
......@@ -51,13 +51,13 @@ class pid {
void reset(float output = 0) {
output_ = output;
last_input_ = 0;
i_term_ = clamp(output_, conf_.min, conf_.max);
i_term_ = std::clamp(output_, conf_.min, conf_.max);
}
void set_config(config conf) {
conf_ = conf;
output_ = clamp(output_, conf_.min, conf_.max);
i_term_ = clamp(i_term_, conf_.min, conf_.max);
output_ = std::clamp(output_, conf_.min, conf_.max);
i_term_ = std::clamp(i_term_, conf_.min, conf_.max);
}
/// call this reularly, the meaning of time value 'now' is up to you, just be consistent
......@@ -76,11 +76,11 @@ class pid {
i_term_ += conf_.i * (error * t_diff);
// we want to prevent the i_term_ to escallate out of proportion, to prevent it from
// going to infinity and beyond
i_term_ = clamp(i_term_, conf_.min, conf_.max);
i_term_ = std::clamp(i_term_, conf_.min, conf_.max);
float input_diff = (input - last_input_) / t_diff;
output_ = conf_.p * error + i_term_ - conf_.d * input_diff;
output_ = clamp(output_, conf_.min, conf_.max);
output_ = std::clamp(output_, conf_.min, conf_.max);
last_input_ = input;
last_time_ = now;
......
#include <array>
#include <tuple>
#include <type_traits>
#include <vector>
#include "emlabcpp/concepts.h"
#include "emlabcpp/types/base.h"
#pragma once
namespace emlabcpp {
// ------------------------------------------------------------------------------------------------
/// iterator_of is structure where iterator_of<Container>::type returns type of iterator that is
/// returned by cont.begin();
template <typename Container>
struct iterator_of {
using type = decltype(std::declval<std::remove_reference_t<Container>>().begin());
};
template <typename Container>
using iterator_of_t = typename iterator_of<Container>::type;
// ------------------------------------------------------------------------------------------------
/// is_view<T>::value marks whenever is some type of temporary view - not owning of the data
namespace detail {
template <typename>
struct is_view_impl : std::false_type {};
} // namespace detail
template <typename T>
constexpr bool is_view_v = detail::is_view_impl<std::decay_t<T>>::value;
// ------------------------------------------------------------------------------------------------
/// are_same<Ts..>::value is true if all Ts... are equal types.
template <typename...>
struct are_same;
template <typename T, typename... Ts>
struct are_same<T, Ts...> : std::conjunction<std::is_same<T, Ts>...> {};
template <>
struct are_same<> : std::true_type {};
template <typename... Ts>
constexpr bool are_same_v = are_same<Ts...>::value;
// ------------------------------------------------------------------------------------------------
/// tuple_has_type<T, Tuple>::value is true if Tuple s std::tuple and contains type T
template <typename T, typename Tuple>
struct tuple_has_type;
template <typename T, typename... Us>
struct tuple_has_type<T, std::tuple<Us...>> : std::disjunction<std::is_same<T, Us>...> {};
template <typename T, typename... Us>
constexpr bool tuple_has_type_v = tuple_has_type<T, Us...>::value;
// ------------------------------------------------------------------------------------------------
/// is_std_tuple<T>::value is true if type T is std::tuple
template <typename>
struct is_std_tuple : std::false_type {};
template <typename... T>
struct is_std_tuple<std::tuple<T...>> : std::true_type {};
template <typename T>
constexpr bool is_std_tuple_v = is_std_tuple<std::decay_t<T>>::value;
// ------------------------------------------------------------------------------------------------
/// is_std_array<T>::value is true if type T is std::array
template <typename>
struct is_std_array : std::false_type {};
template <typename T, std::size_t N>
struct is_std_array<std::array<T, N>> : std::true_type {};
template <typename T>
constexpr bool is_std_array_v = is_std_array<std::decay_t<T>>::value;
// ------------------------------------------------------------------------------------------------
/// is_std_vector<T>::value is true if type T is std::vector
template <typename>
struct is_std_vector : std::false_type {};
template <typename T>
struct is_std_vector<std::vector<T>> : std::true_type {};
template <typename T>
constexpr bool is_std_vector_v = is_std_vector<std::decay_t<T>>::value;
// ------------------------------------------------------------------------------------------------
/// static_size<T>::value is size of the type T, if it has any deducable at compile time
template <typename>
struct static_size;
template <typename T, std::size_t N>
struct static_size<std::array<T, N>> {
static constexpr std::size_t value = N;
};
template <typename... Ts>
struct static_size<std::tuple<Ts...>> {
static constexpr std::size_t value = std::tuple_size_v<std::tuple<Ts...>>;
};
template <typename T>
constexpr std::size_t static_size_v = static_size<std::decay_t<T>>::value;
// ------------------------------------------------------------------------------------------------
/// has_static_size<T>::value is true in case type T have size deduceable at compile time
template <typename T>
struct has_static_size {
template <typename U, typename = decltype(static_size<std::decay_t<U>>::value)>
static std::true_type test(int);
template <typename>
static std::false_type test(...);
static constexpr bool value = decltype(test<T>(0))::value;
};
template <typename T>
constexpr bool has_static_size_v = has_static_size<std::decay_t<T>>::value;
// ------------------------------------------------------------------------------------------------
/// is_container<T>::value is true in case T is datatype with begin()/end() methods
template <typename T>
struct is_container {
template <typename U, typename = decltype(std::declval<U>().begin()),
typename = decltype(std::declval<U>().end())>
static std::true_type test(int);
template <typename>
static std::false_type test(...);
static constexpr bool value = decltype(test<T>(0))::value;
};
template <typename T>
constexpr bool is_container_v = is_container<T>::value;
constexpr bool has_static_size_v = static_sized<T>;
// ------------------------------------------------------------------------------------------------
/// has_push_back<T>::value is true in case type T has T::push_back(T::value_type) method
template <typename T>
struct has_push_back {
template <typename U, typename = decltype(std::declval<U>().push_back(
std::declval<typename U::value_type>()))>
static std::true_type test(int);
template <typename>
static std::false_type test(...);
static constexpr bool value = decltype(test<T>(0))::value;
};
template <typename T>
constexpr bool has_push_back_v = has_push_back<std::decay_t<T>>::value;
// ------------------------------------------------------------------------------------------------
/// @{
/// mapped<T,F>::type is type returned by instance of F::operator() when applied on items from
/// instance of T. It can differentiate between tuples or containers
template <typename Container, typename UnaryFunction>
struct mapped {
using type =
decltype(std::declval<UnaryFunction>()(*std::begin(std::declval<Container>())));
};
struct mapped;
template <typename UnaryFunction, typename T, typename... Ts>
struct mapped<std::tuple<T, Ts...>, UnaryFunction> {
using type = decltype(std::declval<UnaryFunction>()(std::declval<T>()));
template <gettable_container Container, typename UnaryFunction>
struct mapped<Container, UnaryFunction> {
using type = decltype(std::declval<UnaryFunction>()(std::get<0>(std::declval<Container>())));
};
template <typename UnaryFunction, typename T, typename... Ts>
struct mapped<std::tuple<T, Ts...> &, UnaryFunction> {
T item;
using type = decltype(std::declval<UnaryFunction>()(item));
};
template <typename UnaryFunction, typename T, typename... Ts>
struct mapped<const std::tuple<T, Ts...> &, UnaryFunction> {
using type = decltype(std::declval<UnaryFunction>()(std::declval<T>()));
};
template <typename UnaryFunction, typename T, typename... Ts>
struct mapped<std::tuple<T, Ts...> &&, UnaryFunction> {
using type = decltype(std::declval<UnaryFunction>()(std::declval<T>()));
template <range_container Container, typename UnaryFunction>
struct mapped<Container, UnaryFunction> {
using type =
decltype(std::declval<UnaryFunction>()(*std::begin(std::declval<Container>())));
};
///@}
template <typename Container, typename UnaryFunction>
using mapped_t = typename mapped<Container, UnaryFunction>::type;
......
#include <array>
#include <tuple>
#include <type_traits>
#include <vector>
#pragma once
namespace emlabcpp {
// ------------------------------------------------------------------------------------------------
/// iterator_of is structure where iterator_of<Container>::type returns type of iterator that is
/// returned by cont.begin();
template <typename Container>
struct iterator_of {
using type = decltype(std::begin(std::declval<std::remove_reference_t<Container>>()));
};
template <typename Container>
using iterator_of_t = typename iterator_of<Container>::type;
// ------------------------------------------------------------------------------------------------
/// is_view<T>::value marks whenever is some type of temporary view - not owning of the data
namespace impl {
template <typename>
struct is_view : std::false_type {};
template <std::ranges::borrowed_range T>
struct is_view<T> : std::true_type {};
} // namespace impl