mikage-dev/source/framework/bit_field_new.hpp
2024-12-08 20:12:38 +01:00

374 lines
15 KiB
C++

#pragma once
#include <climits>
#include <cstdint>
#include <cstddef>
#include <type_traits>
#include <utility>
namespace v2 {
namespace BitField {
template<typename>
struct Fields;
namespace detail {
// TODO: Consider adding a policy parameter; a candidate for a policy is bounds-checking and wrapping/clamping the argument to Set()
template<std::size_t Position, std::size_t Bits, typename ParentClass, typename Storage, typename Exposure = Storage>
class Field {
friend struct Fields<Storage>;
ParentClass object;
constexpr Field(ParentClass object) : object(object) {}
// Store intermediate computations as integral_constants to make sure even the dumbest compiler can inline our member functions
// NOTE: the double Storage cast in full_mask is intended: E.g. if Storage is uint64_t and assuming 0 is of type int32_t, ~0 wouldn't match the expected 0xffffffffffffffff. On the other hand, if Storage is unsigned char, ~(Storage)0 gets promoted to the int value -1.
static constexpr auto full_mask = std::integral_constant<Storage, static_cast<Storage>(~Storage{0})>::value;
static constexpr auto field_mask = std::integral_constant<Storage, ((full_mask >> (8 * sizeof(Storage) - Bits)) << Position)>::value;
static constexpr auto neg_field_mask = std::integral_constant<Storage, static_cast<Storage>(~field_mask)>::value;
public:
operator Exposure() const {
return static_cast<Exposure>((object.storage & field_mask) >> Position);
}
// Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance)
[[nodiscard]] ParentClass Set(Exposure value) const {
//static_assert(std::is_base_of_v<BitsOnBase, ParentClass>, "Given ParentClass does not inherit BitsOnBase");
static_assert(sizeof(ParentClass) == sizeof(Storage), "ParentClass has additional members other than storage");
auto ret = object;
ret.storage = (object.storage & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask);
return ret;
}
/// Concise version of casting to Exposure
Exposure operator ()() const {
return static_cast<Exposure>(*this);
}
/// Concise version of Set(value)
[[nodiscard]] ParentClass operator ()(Exposure value) const {
return Set(value);
}
static constexpr Exposure MaxValue() {
// TODO: This may overflow (e.g. Bits=32, Exposure=uint32_t)
return static_cast<Exposure>((Storage{1} << Bits) - 1);
}
// TODO: SignedBitField
};
// Base class defining the Field substruct. Expects ParentClass to have a single (inherited or non-inherited) member called "storage" of unsigned type (custom arithmetic allowed, howver, e.g. to implement endianness conversion).
template<typename Storage, typename ParentClass>
struct BitsOnBase {
template<std::size_t Pos, std::size_t Bits, typename Exposure = Storage>
auto MakeField() const {
return Fields<Storage>{}.Make();
}
};
} // namespace detail
/// BitFields on a given Storage type
/// TODO: Consider refactoring MakeOn to be a static constexpr function object rather than a function
template<typename Storage>
struct Fields {
template<std::size_t Pos, std::size_t Bits, typename ParentClass, typename Exposure = Storage>
static auto MakeOn(ParentClass obj) {
return detail::Field<Pos, Bits, ParentClass, Storage, Exposure>{ obj };
}
/// Convenience overload to allow directly passing in a this pointer
template<std::size_t Pos, std::size_t Bits, typename ParentClass, typename Exposure = Storage>
static auto MakeOn(const ParentClass* obj) {
return detail::Field<Pos, Bits, ParentClass, Storage, Exposure>{ *obj };
}
/// TODO: Privatize
template<std::size_t Pos, std::size_t Bits, typename Exposure>
struct MakeOn2Type {
template<typename ParentClass>
constexpr auto operator()(ParentClass obj) const {
return detail::Field<Pos, Bits, ParentClass, Storage, Exposure>{ obj };
}
};
template<std::size_t Pos, std::size_t Bits, typename Exposure = Storage>
static constexpr auto MakeOn2 = MakeOn2Type<Pos, Bits, Exposure>{};
};
struct OwnStoragePolicy {};
/**
* Convenience class to allow for terser syntax and to automatially allocate storage.
* Child classes only define the actual fields.
*/
template<typename Storage, typename ParentClass, typename Policy = OwnStoragePolicy>
struct BitsOn : detail::BitsOnBase<Storage, ParentClass> {
// Rely on ParentClass providing a "storage" member unless ProvideStorage is true (see specialization below)
};
template<typename Storage, typename ParentClass>
struct BitsOn<Storage, ParentClass, OwnStoragePolicy> : detail::BitsOnBase<Storage, ParentClass> {
// Automatically allocate the "storage" member
Storage storage;
};
} // namespace BitField
} // namespace v2
namespace v3 {
namespace BitField {
namespace detail {
template<std::size_t Bits>
using uint_least_n_bits =
std::conditional_t<Bits <= sizeof(std::uint_least8_t) * CHAR_BIT, std::uint_least8_t,
std::conditional_t<Bits <= sizeof(std::uint_least16_t) * CHAR_BIT, std::uint_least16_t,
std::conditional_t<Bits <= sizeof(std::uint_least32_t) * CHAR_BIT, std::uint_least32_t,
std::conditional_t<Bits <= sizeof(std::uint_least64_t) * CHAR_BIT, std::uint_least64_t,
std::uintmax_t>>>>;
template<std::size_t Position, std::size_t Bits, typename ParentClass, typename Storage, typename Exposure = Storage>
class Field {
public:
ParentClass object;
constexpr Field(ParentClass object) : object(object) {}
// Store intermediate computations as integral_constants to make sure even the dumbest compiler can inline our member functions
// NOTE: the double Storage cast in full_mask is intended: E.g. if Storage is uint64_t and assuming 0 is of type int32_t, ~0 wouldn't match the expected 0xffffffffffffffff. On the other hand, if Storage is unsigned char, ~(Storage)0 gets promoted to the int value -1.
static constexpr auto full_mask = std::integral_constant<Storage, static_cast<Storage>(~Storage{0})>::value;
static constexpr auto field_mask = std::integral_constant<Storage, ((full_mask >> (8 * sizeof(Storage) - Bits)) << Position)>::value;
static constexpr auto neg_field_mask = std::integral_constant<Storage, static_cast<Storage>(~field_mask)>::value;
// TODO: static_assert the Exposure type can store up to Bits bits. Refer to "operator Exposure()" to see how to distinguish the various cases
public:
constexpr operator Exposure() const {
// Extract the bit field value and convert it to Exposure. The latter step is trivial for scalar Exposures, but can be tricky for class-types:
// * For aggregates, assign through a structured binding (because brace-initialization could be narrowing)
if constexpr (std::is_class_v<ParentClass>) {
auto [storage] = object;
if constexpr (std::is_scalar_v<Exposure>) {
return static_cast<Exposure>((storage & field_mask) >> Position);
} else {
// * use aggregate-initialization for aggregate types, or
// * call single-argument constructor for non-aggregate types
//
// In both cases, the result type of the bit field extraction
// may be larger than the aggregate member or constructor
// argument. To prevent narrowing warnings in this case, we
// static_cast the intermediate value to the smallest integer
// that can hold Bits bits
// TODO: How well will this work with signed storages?
// TODO: Consider adding a customization point for this conversion
return Exposure { static_cast<uint_least_n_bits<Bits>>((storage & field_mask) >> Position) };
}
} else {
auto storage = object;
// return Exposure { (storage & field_mask) >> Position };
return static_cast<Exposure>((storage & field_mask) >> Position);
}
}
// Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance)
[[nodiscard]] constexpr ParentClass Set(Exposure value) const {
//static_assert(std::is_base_of_v<BitsOnBase, ParentClass>, "Given ParentClass does not inherit BitsOnBase");
static_assert(sizeof(ParentClass) == sizeof(Storage), "ParentClass has additional members other than storage");
auto ret = object;
if constexpr (std::is_class_v<ParentClass>) {
auto& [storage] = ret;
storage = (storage & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask);
} else {
auto storage = object;
ret = (storage & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask);
}
return ret;
}
/// Concise version of casting to Exposure
constexpr Exposure operator ()() const {
return static_cast<Exposure>(*this);
}
/// Concise version of Set(value)
[[nodiscard]] constexpr ParentClass operator ()(Exposure value) const {
return Set(value);
}
static constexpr Exposure MaxValue() {
return Exposure { (Storage{1} << Bits) - 1 };
}
static constexpr std::size_t NumBits() noexcept {
return Bits;
}
};
template<typename T>
constexpr auto GetStorage(const T& t) {
auto& [storage] = t;
return storage;
}
// TODO: Instead of using a PointerToMember here, a generic lens would allow for accessing e.g. arrays!
// TODO: Define generic Lens concept: Must be a callable with signature Class -> (Member -> Member) -> (Member, Class)
template<size_t Position, size_t Bits, typename ParentClass, typename MemberLens,
typename Exposure = typename MemberLens::MemberType>
class FieldNew {
using Storage = typename MemberLens::MemberType;
public:
ParentClass object;
constexpr FieldNew(ParentClass object) : object(object) {}
// Store intermediate computations as integral_constants to make sure even the dumbest compiler can inline our member functions
// NOTE: the double Storage cast in full_mask is intended: E.g. if Storage is uint64_t and assuming 0 is of type int32_t, ~0 wouldn't match the expected 0xffffffffffffffff. On the other hand, if Storage is unsigned char, ~(Storage)0 gets promoted to the int value -1.
static constexpr auto full_mask = std::integral_constant<Storage, static_cast<Storage>(~Storage{0})>::value;
static constexpr auto field_mask = std::integral_constant<Storage, ((full_mask >> (8 * sizeof(Storage) - Bits)) << Position)>::value;
static constexpr auto neg_field_mask = std::integral_constant<Storage, static_cast<Storage>(~field_mask)>::value;
public:
constexpr operator Exposure() const {
return static_cast<Exposure>((MemberLens::get(object) & field_mask) >> Position);
}
// Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance)
[[nodiscard]] ParentClass Set(Exposure value) const {
return MemberLens::modify( *this,
[&](const auto& data) { return (data & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask); });
}
/// Concise version of casting to Exposure
constexpr Exposure operator ()() const {
return static_cast<Exposure>(*this);
}
/// Concise version of Set(value)
[[nodiscard]] constexpr ParentClass operator ()(Exposure value) const {
return Set(value);
}
static constexpr Exposure MaxValue() {
// TODO: This may overflow (e.g. Bits=32, Exposure=uint32_t)
return static_cast<Exposure>((Storage{1} << Bits) - 1);
}
// TODO: SignedBitField
};
template<auto PointerToMember>
struct PMDLens;
template<typename Member, typename Class, Member Class::*PointerToMember>
struct PMDLens<PointerToMember> {
using MemberType = Member;
template<typename Modifier>
static Class modify(Class&& object, Modifier&& modifier) {
// TODO: Should avoid copy for rvalue objects
auto ret = std::forward<Class>(object);
ret.*PointerToMember = std::forward<Modifier>(modifier)(ret.*PointerToMember);
return ret;
}
template<typename Modifier>
static Class modify(const Class& object, Modifier&& modifier) {
auto ret = object;
ret.*PointerToMember = std::forward<Modifier>(modifier)(ret.*PointerToMember);
return ret;
}
static MemberType get(Class&& object) {
return std::forward<Class>(object).*PointerToMember;
}
static MemberType get(const Class& object) {
return object.*PointerToMember;
}
};
} // namespace detail
template<std::size_t Position, std::size_t Bits, typename Exposure, typename ParentClass>
constexpr auto MakeFieldOn(const ParentClass* t) {
if constexpr (std::is_class_v<ParentClass>) {
using Storage = decltype(detail::GetStorage(std::declval<ParentClass>()));
return detail::Field<Position, Bits, ParentClass, Storage, Exposure> { *t };
} else {
return detail::Field<Position, Bits, ParentClass, ParentClass, Exposure> { *t };
}
}
template<std::size_t Position, std::size_t Bits, typename ParentClass>
constexpr auto MakeFieldOn(const ParentClass* t) {
if constexpr (std::is_class_v<ParentClass>) {
using Storage = decltype(detail::GetStorage(std::declval<ParentClass>()));
return detail::Field<Position, Bits, ParentClass, Storage, Storage> { *t };
} else {
return detail::Field<Position, Bits, ParentClass, ParentClass, ParentClass> { *t };
}
}
// Usage, e.g.: auto enabled() const { return BitField::v3::MakeFlagOn< 0, &StencilTest::raw1>(this); }
template<auto member, size_t Position, size_t Bits, typename Exposure = typename detail::PMDLens<member>::MemberType, typename ParentClass>
constexpr detail::FieldNew<Position, Bits, ParentClass, detail::PMDLens<member>, Exposure>
MakeFieldOn(const ParentClass* t) {
static_assert(std::is_class_v<ParentClass>, "Must only call this overload on class types");
static_assert(std::is_member_pointer_v<decltype(member)>, "Given member must be a pointer-to-member");
static_assert(std::is_invocable_v<decltype(member), ParentClass>, "Given object type is not compatible with the pointer-to-member");
return { *t };
}
template<auto member, size_t Position, typename ParentClass>
constexpr detail::FieldNew<Position, 1, ParentClass, detail::PMDLens<member>, bool>
MakeFlagOn(const ParentClass* t) {
static_assert(std::is_class_v<ParentClass>, "Must only call this overload on class types");
static_assert(std::is_member_pointer_v<decltype(member)>, "Given member must be a pointer-to-member");
static_assert(std::is_invocable_v<decltype(member), ParentClass>, "Given object type is not compatible with the pointer-to-member");
return { *t };
}
template<std::size_t Position, typename ParentClass>
constexpr auto MakeFlagOn(const ParentClass* t) {
if constexpr (std::is_class_v<ParentClass>) {
using Storage = decltype(detail::GetStorage(std::declval<ParentClass>()));
return detail::Field<Position, 1, ParentClass, Storage, bool> { *t };
} else {
return detail::Field<Position, 1, ParentClass, ParentClass, bool> { *t };
}
}
} // namespace BitField
} // namespace v3
namespace BitField {
namespace v2 {
using namespace ::v2::BitField;
}
namespace v3 {
using namespace ::v3::BitField;
}
}