341 lines
13 KiB
C++
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
|