larra/library/include/larra/utils.hpp

341 lines
13 KiB
C++

#pragma once
#include <boost/pfr.hpp>
#include <nameof.hpp>
#include <ranges>
#include <utempl/utils.hpp>
namespace larra::xmpp::utils {
namespace impl {
template <typename T, typename R>
constexpr auto GetType(R(T::* ptr)) -> R;
template <typename T>
using GetTypeT = decltype(GetType(std::declval<T>()));
template <typename Self, typename Type, typename... Fs>
concept SetConcept =
(std::conditional_t<std::same_as<GetTypeT<Fs>, Type>,
std::true_type,
std::integral_constant<
bool,
std::is_reference_v<Self>
? std::copy_constructible<GetTypeT<Fs>>
: std::move_constructible<GetTypeT<Fs>>&& requires { std::declval<Self>().*std::declval<Fs>(); }>>::value &&
...);
} // namespace impl
// Fields description
// struct SomeStruct {
// std::string field1;
// std::string field2;
// static constexpr /* FieldsDescription */ auto kUtils = CreateFieldsDescription(&SomeStruct::field1, &SomeStruct::field2);
//
// template <typename Self>
// constexpr auto Field1(this Self&& self, std::string field1) -> SomeStruct {
// return kUtils.With<0>(std::forward<Self>(self), std::move(field1)); // Return new object with field1 value from field1 and with
// // field2 value from self.field2
// }
// template <typename Self>
// constexpr auto Field2(this Self&& self, std::string field2) -> SomeStruct {
// return kUtils.With(&SomeStruct::field2, std::forward<Self>(self), std::move(field2)); // With field2
// }
// };
template <typename T, typename... Fs>
struct FieldsDescription {
utempl::Tuple<Fs...> tuple; /*!< tuple for field ptrs */
/* Method accepting field index, self and new value for field and returns object with new field value
*
* \tparam I field index for object T
* \tparam Type return type
* \param self old object
* \param value new value for field
* \return an object of type std::decay_t<Self> with data from T with fields from \ref self and the \ref value of the field with index
* \ref I
*/
template <std::size_t I, typename Self, typename Value> // NOLINTNEXTLINE
constexpr auto With(Self&& self, Value&& value) const -> std::decay_t<Self>
requires(std::constructible_from<std::decay_t<Self>, T> && std::is_constructible_v<T, impl::GetTypeT<Fs>...> &&
impl::SetConcept<Self, std::decay_t<Value>, Fs...> &&
[] {
if constexpr(I < sizeof...(Fs)) {
return std::is_constructible_v<impl::GetTypeT<decltype(Get<I>(tuple))>, decltype(value)>;
};
return false;
}())
{
return std::decay_t<Self>{[&](auto... is) -> T {
return {[&] -> decltype(auto) {
if constexpr(*is == I) {
return std::forward<Value>(value);
} else {
return std::forward_like<Self>(self.*Get<*is>(this->tuple));
};
}()...};
} | utempl::kSeq<sizeof...(Fs)>};
};
/* Method accepting field pointer, self and new value for field and returns object with new field value
*
* \param ptr field ptr for object T
* \param self old object
* \param value new value for field
* \return an object of type std::decay_t<Self> with data from T with fields from \ref self and the \ref value of the field \ref ptr
*/
template <typename Self, typename Value, typename Type>
constexpr auto With(Type(T::* ptr), Self&& self, Value&& value) const // NOLINT(cppcoreguidelines-missing-std-forward)
requires(std::is_constructible_v<T, impl::GetTypeT<Fs>...> && std::is_constructible_v<std::decay_t<Self>, T> &&
std::is_constructible_v<Type, decltype(value)> && impl::SetConcept<Self, Type, Fs...>)
{
return std::decay_t<Self>{utempl::Unpack(this->tuple, [&](auto... fs) -> T {
return {[&] {
if constexpr(std::is_same_v<decltype(fs), decltype(ptr)>) {
if(fs == ptr) {
return std::forward<Value>(value);
};
};
return std::forward_like<Self>(self.*fs);
}()...};
})};
};
}; // namespace larra::xmpp::utils
namespace impl {
template <typename T, typename... Fs>
consteval auto CreateFieldsDescriptionHelper(Fs&&... fs) -> FieldsDescription<T, std::decay_t<Fs>...> {
return {.tuple = {std::forward<Fs>(fs)...}};
}
// Bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109781
template <typename T, typename Self>
consteval auto GCCWorkaround() -> decltype(auto) {
if constexpr(std::same_as<T, void>) {
return [] -> Self {
std::unreachable();
}();
} else {
return [] -> std::remove_reference_t<decltype(std::forward_like<Self>(std::declval<T>()))> {
std::unreachable();
}();
}
}
} // namespace impl
/* Method accepting field ptrs and returns FieldsDescription
*
* \param ptrs field ptrs
* \return FieldsDescription with T and ptrs
*/
template <typename T, typename... Rs>
consteval auto CreateFieldsDescription(Rs(T::*... ptrs)) {
return [&](auto... is) {
return impl::CreateFieldsDescriptionHelper<T>(ptrs...);
} | utempl::kSeq<sizeof...(Rs)>;
};
// Field set helper
// struct SomeStruct {
// std::string field1;
// std::string field2;
//
// template <typename Self>
// auto Field1(this Self&& self, std::string field1) -> SomeStruct {
// return FieldSetHelper::With<0>(std::forward<Self>(self), std::move(field1)); // Return new object with field1 value from field1
// // and with field2 value from self.field2
// }
// template <typename Self>
// auto Field2(this Self&& self, std::string field2) -> SomeStruct {
// return FieldSetHelper::With<"field2">(std::forward<Self>(self), std::move(field2)); // With field2
// }
// };
struct FieldSetHelper {
/* Method accepting field index, self and new value for field and returns object with new field value
*
* \tparam I field index for object T
* \tparam T Type for return and pfr reflection
* \param self old object
* \param value new value for field
* \returns an object of type std::decay_t<Self> with data from Type constructed with fields with \ref self and the \ref value of the
* field with index \ref I where Type is std::conditional_t<std::same_as<\ref T, void>, std::decay_t<Self>, T>
*/
template <std::size_t I,
typename T = void,
bool ConstructT = true,
typename Self,
typename Value,
typename...,
typename Type = std::conditional_t<std::same_as<T, void>, std::decay_t<Self>, T>,
typename TT = decltype(impl::GCCWorkaround<T, Self>())> // NOLINTNEXTLINE
static constexpr auto With(Self&& self, Value&& value) -> std::conditional_t<ConstructT, std::decay_t<Self>, Type>
requires(std::is_aggregate_v<Type> &&
(!ConstructT || (std::constructible_from<std::decay_t<Self>, Type> &&
(std::same_as<T, void> || requires { static_cast<const T&>(self); }))) &&
I < boost::pfr::tuple_size_v<Type> &&
std::is_constructible_v<std::decay_t<decltype(boost::pfr::get<I>(std::declval<TT>()))>, decltype(value)> &&
[](auto... is) {
return ((*is == I ? true
: std::is_reference_v<Self> ? std::copy_constructible<decltype(boost::pfr::get<*is>(std::declval<TT>()))>
: std::move_constructible<decltype(boost::pfr::get<*is>(std::declval<TT>()))>) &&
...) &&
std::is_constructible_v<Type, decltype(boost::pfr::get<*is>(std::declval<TT>()))...>;
} | utempl::kSeq<boost::pfr::tuple_size_v<Type>>)
{
return std::conditional_t<ConstructT, std::decay_t<Self>, Type>{[&](auto... is) -> Type {
return {[&] -> decltype(auto) {
if constexpr(I == *is) {
return std::forward<Value>(value);
} else {
return std::forward_like<Self>(boost::pfr::get<*is>(static_cast<std::conditional_t<ConstructT, TT&, decltype(self)>>(self)));
}
}()...};
} | utempl::kSeq<boost::pfr::tuple_size_v<TT>>};
};
// clang-format off
/* Method accepting field name, self and new value for field and returns object with new field value
*
* \param FieldName field name for object T
* \tparam T Type for return and pfr reflection
* \param self old object
* \param value new value for field
* \returns an object of type std::decay_t<Self> with data from Type constructed with fields with \ref self and the \ref value of the field named \ref FieldName where Type is
* std::conditional_t<std::same_as<\ref T, void>, std::decay_t<Self>, \ref T>
*/
template <utempl::ConstexprString FieldName, typename T = void, bool ConstructT = true, typename Self, typename Value>
static constexpr auto With(Self&& self, Value&& value) -> decltype(With<[] {
constexpr auto names = boost::pfr::names_as_array<std::conditional_t<std::same_as<T, void>, std::decay_t<Self>, T>>();
return std::ranges::find(names, static_cast<std::string_view>(FieldName)) - names.begin();
}(),
T, ConstructT>(std::forward<Self>(self), std::forward<Value>(value))) {
constexpr auto names = boost::pfr::names_as_array<std::conditional_t<std::same_as<T, void>, std::decay_t<Self>, T>>();
constexpr auto id = std::ranges::find(names, static_cast<std::string_view>(FieldName)) - names.begin();
return With<id, T, ConstructT>(std::forward<Self>(self), std::forward<Value>(value));
};
// clang-format on
};
/*
template <typename Range, typename Delim>
SplitView(Range&& range, Delim&& delim) -> SplitView<std::views::all_t<Range>, Delim>;
*/
#if __has_cpp_attribute(__cpp_lib_start_lifetime_as)
template <typename T>
inline auto StartLifetimeAsArray(void* ptr, std::size_t n) -> T* {
return std::start_lifetime_as_array<T>(ptr, n);
}
template <typename T>
inline auto StartLifetimeAs(void* ptr) -> T* {
return std::start_lifetime_as<T>(ptr);
}
template <typename T>
inline auto StartLifetimeAsArray(const void* ptr, std::size_t n) -> const T* {
return std::start_lifetime_as_array<T>(ptr, n);
}
template <typename T>
inline auto StartLifetimeAs(const void* ptr) -> const T* {
return std::start_lifetime_as<T>(ptr);
}
#else
template <typename T>
inline auto StartLifetimeAsArray(void* ptr, std::size_t n) -> T* {
return std::launder(reinterpret_cast<T*>(new(ptr) std::byte[n * sizeof(T)]));
}
template <typename T>
inline auto StartLifetimeAs(void* ptr) -> T* {
return StartLifetimeAsArray<T>(ptr, 1);
}
template <typename T>
inline auto StartLifetimeAsArray(const void* ptr, std::size_t n) -> const T* {
return std::launder(reinterpret_cast<const T*>(new(const_cast<void*>(ptr)) std::byte[n * sizeof(T)])); // NOLINT
}
template <typename T>
inline auto StartLifetimeAs(const void* ptr) -> const T* {
return StartLifetimeAsArray<T>(ptr, 1);
}
#endif
// Example: "Hello" | std::ranges::to<RangeToWrapper<std::array<char, 6>>>()
template <typename T>
struct RangeToWrapper : T {
[[nodiscard]] constexpr auto Base() & -> decltype(auto) {
return static_cast<T&>(*this);
}
[[nodiscard]] constexpr auto Base() const& -> decltype(auto) {
return static_cast<const T&>(*this);
}
[[nodiscard]] constexpr auto Base() && -> decltype(auto) {
return static_cast<T&&>(*this);
}
[[nodiscard]] constexpr auto Base() const&& -> decltype(auto) {
return static_cast<const T&&>(*this);
}
template <typename Range> // NOLINTNEXTLINE
constexpr RangeToWrapper(std::from_range_t, Range&& range) {
auto result = std::ranges::copy(range, this->begin());
if(result.in != std::ranges::end(range) || result.out != std::ranges::end(*this)) {
throw std::invalid_argument{"Invalid range size"};
}
}
template <typename... Args> // NOLINTNEXTLINE
constexpr RangeToWrapper(Args&&... args)
requires requires { T{std::forward<Args>(args)...}; }
: T{std::forward<Args>(args)...} {};
};
template <typename T>
concept LengthCalculatable = requires(const T& obj) {
{ obj.length() } -> std::convertible_to<std::size_t>;
} || std::convertible_to<T, std::string>;
template <typename T>
auto AccumulateFieldLength(const T& obj) -> std::size_t {
std::size_t totalLength = 0;
boost::pfr::for_each_field(obj, [&](const LengthCalculatable auto& field) {
totalLength += field.length(); // Accumulate length of each field
});
return totalLength;
}
template <typename T>
constexpr auto ToKebabCaseName() -> std::string_view {
static constexpr auto rawStr = nameof::nameof_short_type<T>();
// std::isupper and std::tolower are not declared as constexpr
static constexpr auto isUpper = [](char ch) {
return ch >= 'A' && ch <= 'Z';
};
static constexpr auto toLower = [](char ch) {
return (ch >= 'A' && ch <= 'Z') ? static_cast<char>(ch + ('a' - 'A')) : ch;
};
constexpr auto str = [] {
return rawStr //
| std::views::transform([](auto ch) {
return isUpper(ch) ? std::array<char, 2>{'-', toLower(ch)} : std::array<char, 2>{ch, '\0'};
}) //
| std::views::join //
| std::views::filter([](char ch) {
return ch != '\0';
}) //
| std::views::drop(1);
};
static constexpr auto arr = str() | std::ranges::to<utils::RangeToWrapper<std::array<char, std::ranges::distance(str())>>>();
return {arr.data(), arr.size()};
}
} // namespace larra::xmpp::utils