Skip to content
55 changes: 55 additions & 0 deletions include/iris/math.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef IRIS_ZZ_MATH_HPP
#define IRIS_ZZ_MATH_HPP

// SPDX-License-Identifier: MIT

#include <iris/stdint.hpp>

#include <concepts>
#include <bit>
#include <limits>

#include <cstddef> // IWYU pragma: keep
#include <cmath> // IWYU pragma: keep

namespace iris {

namespace detail {

template<class T>
concept bit_inspectable_floating_point =
std::floating_point<T> &&
std::numeric_limits<T>::radix == 2 &&
requires { typename unsigned_integer_of_size_t<sizeof(T)>; };

} // detail

template<std::floating_point T>
[[nodiscard]] constexpr bool isnan(T const x) noexcept
{
#if __cpp_lib_constexpr_cmath >= 202202L
return std::isnan(x);
#else
if constexpr (detail::bit_inspectable_floating_point<T>) {
using uint = unsigned_integer_of_size_t<sizeof(T)>;
constexpr int mantissa_bits = std::numeric_limits<T>::digits - 1;
constexpr int exponent_bits = int(sizeof(T) * 8) - 1 - mantissa_bits;
constexpr uint mantissa_mask = (uint(1) << mantissa_bits) - 1;
constexpr uint exponent_mask = ((uint(1) << exponent_bits) - 1) << mantissa_bits;
uint const bits = std::bit_cast<uint>(x);
return (bits & exponent_mask) == exponent_mask && (bits & mantissa_mask) != 0;
} else {
return x != x;
}
#endif
}

template<std::integral T>
[[nodiscard]] constexpr bool isnan(T) noexcept
{
return false;
}

} // iris

#endif
3 changes: 2 additions & 1 deletion include/iris/stdint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ namespace iris {
namespace detail {

template<bool IsSigned, std::size_t Bytes>
struct integer_of_size_impl; // undefined
struct integer_of_size_impl
{};

template<>
struct integer_of_size_impl<true, 1>
Expand Down
91 changes: 84 additions & 7 deletions include/iris/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,75 @@ struct remove_cv<T const volatile>
using apply = F<T> const volatile;
};

// -----------------------------------------------------------

namespace detail {

template<class Cand, class T>
concept dominant_type_candidate_dominates =
requires { typename std::common_type_t<T, std::decay_t<Cand>>; } &&
std::same_as<std::common_type_t<T, std::decay_t<Cand>>, std::decay_t<Cand>>;

template<class Cand, class... Ts>
struct dominant_type_candidate
: std::bool_constant<(dominant_type_candidate_dominates<Cand, Ts> && ...)>
{
using type = std::decay_t<Cand>;
};

} // detail

// The type among `Ts...` that dominates all the others, i.e., the `Cand` in `Ts...`
// such that `std::common_type_t<T, Cand>` is `Cand` for every `T`.
//
// The result is always one of `Ts...` and does not depend on their order; no `type`
// member if no such type exists (e.g. `short` and `char`, whose common type `int` is
// not among them).
template<class... Ts>
struct dominant_type
{
// No `::type`
};
template<class... Ts>
using dominant_type_t = dominant_type<Ts...>::type;

template<class... Ts>
requires std::disjunction<detail::dominant_type_candidate<Ts, Ts...>...>::value
struct dominant_type<Ts...>
: std::disjunction<detail::dominant_type_candidate<Ts, Ts...>...>
{};

// `T` dominates every type in `Ts...`, i.e., `std::common_type_t<U, T>` is `T` for
// every `U` in `Ts...`.
//
// Can be used to constrain the explicitness of conversion or assignment.
template<class T, class... Ts>
concept dominant = (detail::dominant_type_candidate_dominates<T, Ts> && ...);

// Denotes `iris::dominant_type<Ts...>` if it exists, otherwise equivalent to `std::common_type`.
//
// `std::common_type_t<float, std::float16_t, std::bfloat16_t>`
// -> `float`
// `std::common_type_t<std::float16_t, std::bfloat16_t, float>`
// -> ill-formed
// `iris::symmetric_common_type_t<std::float16_t, std::bfloat16_t, float>`
// -> `float` (in any order)
//
// Use `iris::dominant_type` instead when the result must be one of `Ts...`.
template<class... Ts>
struct symmetric_common_type : std::common_type<Ts...>
{};
template<class... Ts>
using symmetric_common_type_t = symmetric_common_type<Ts...>::type;

template<class... Ts>
requires requires {
typename dominant_type<Ts...>::type;
}
struct symmetric_common_type<Ts...> : dominant_type<Ts...>
{};

// -----------------------------------------------------------

template<class... Ts>
struct type_list
Expand All @@ -89,8 +158,6 @@ struct constant_list
static constexpr std::size_t size = sizeof...(Vals);
};

template<auto...> using cvoid_t = void;

namespace detail {

template<class Voids>
Expand All @@ -100,7 +167,11 @@ template<std::size_t... Voids>
struct do_pack_indexing<std::index_sequence<Voids...>>
{
template<class T>
static T select(cvoid_t<Voids>*..., std::type_identity<T>*, ...);
static std::type_identity<T> select(
decltype(void(Voids), static_cast<void*>(nullptr))...,
std::type_identity<T>*,
...
);
};

template<class Voids>
Expand All @@ -110,7 +181,11 @@ template<std::size_t... Voids>
struct do_cpack_indexing<std::index_sequence<Voids...>>
{
template<class T, T N>
static std::integral_constant<T, N> select(cvoid_t<Voids>*..., std::integral_constant<T, N>*, ...);
static std::integral_constant<T, N> select(
decltype(void(Voids), static_cast<void*>(nullptr))...,
std::integral_constant<T, N>*,
...
);
};

} // detail
Expand All @@ -124,7 +199,9 @@ using at_c_t = at_c<I, T>::type;

// Has native pack indexing?
// Note: GCC 15 emits "sorry, unimplemented: mangling type pack index"
#if !(defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 15) && __cpp_pack_indexing >= 202311L
#if __cpp_pack_indexing >= 202311L && \
!(defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 15) && \
!defined(__clang__)

# define IRIS_PACK_INDEXING(I, Ts_ellipsis) Ts_ellipsis[I]

Expand All @@ -147,7 +224,7 @@ struct pack_indexing
static_assert(I < sizeof...(Ts));
using type = decltype(detail::do_pack_indexing<std::make_index_sequence<I>>::select(
static_cast<std::type_identity<Ts>*>(nullptr)...
));
))::type;
};

template<std::size_t I, class... Ts>
Expand Down Expand Up @@ -175,7 +252,7 @@ struct at_c<I, TT<Ts...>>
static_assert(I < sizeof...(Ts));
using type = decltype(detail::do_pack_indexing<std::make_index_sequence<I>>::select(
static_cast<std::type_identity<Ts>*>(nullptr)...
));
))::type;
};
#endif

Expand Down
77 changes: 77 additions & 0 deletions include/iris/units/concepts.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#ifndef IRIS_ZZ_UNITS_CONCEPTS_HPP
#define IRIS_ZZ_UNITS_CONCEPTS_HPP

// SPDX-License-Identifier: MIT

#include <iris/config.hpp> // IWYU pragma: keep

#include <iris/type_traits.hpp>

#include <concepts>
#include <type_traits>
#include <utility>

namespace iris::units {

template<class T>
using scalar_t = std::remove_cvref_t<decltype(std::declval<T const&>() / std::declval<T const&>())>;

template<class T>
concept scalar = iris::numeric_arithmetic<T>;

// `T` models `ordered_linear` if `T` is a totally ordered one-dimensional
// vector space over `scalar_t<T>`.
//
// For example, a class derived from `iris::units::quantity<Rep>` models
// `ordered_linear` when `Rep` is a floating-point type. Integral `Rep`
// does not, because integer division is not the inverse of multiplication.
//
// Preconditions:
// Values of `T` (and of `scalar_t<T>`) passed to algorithms constrained by
// this concept satisfy the following conditions:
// - They are not NaN.
// - They are finite. In particular, infinity must not be used as a sentinel
// for an unbounded or invalid value.
// - Arithmetic among them does not overflow to infinity.
//
// Semantic requirements are as follows:
// - Let `a`, `b`, and `c` denote instances of `T`.
// - Let `s` denote an instance of `scalar_t<T>`.
// - `T{}` is the additive identity: `a + T{} == a`.
// - `a <= b` implies `a + c <= b + c`.
// - `T{} <= a` and `0 <= s` imply `T{} <= a * s`.
// - For `b != T{}`, `(a / b) * b` approximates `a` within floating-point rounding.
template<class T>
concept ordered_linear =
std::regular<T> &&
std::totally_ordered<T> &&
requires {
typename scalar_t<T>;
} &&
requires(T const& t, scalar_t<T> const& s) {
{ t + t } -> std::same_as<T>;
{ t - t } -> std::same_as<T>;
{ t * s } -> std::same_as<T>;
{ t / s } -> std::same_as<T>;
{ t / t } -> scalar;
} &&
std::floating_point<scalar_t<T>> /* scalars must form a field */;

template<class T>
concept nothrow_ordered_linear =
ordered_linear<T> &&
std::is_nothrow_default_constructible_v<T> &&
std::is_nothrow_copy_constructible_v<T> &&
std::is_nothrow_copy_assignable_v<T> &&
requires(T const& t, scalar_t<T> const& s) {
{ t + t } noexcept;
{ t - t } noexcept;
{ t * s } noexcept;
{ t / s } noexcept;
{ t / t } noexcept;
{ t <= t } noexcept;
};

} // iris::units

#endif
Loading
Loading