#pragma once #include #include #include #include #include namespace v2 { namespace BitField { template 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 class Field { friend struct Fields; 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{0})>::value; static constexpr auto field_mask = std::integral_constant> (8 * sizeof(Storage) - Bits)) << Position)>::value; static constexpr auto neg_field_mask = std::integral_constant(~field_mask)>::value; public: operator Exposure() const { return static_cast((object.storage & field_mask) >> Position); } // Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance) ParentClass Set(Exposure value) const { //static_assert(std::is_base_of_v, "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(value) << Position) & field_mask); return ret; } /// Concise version of casting to Exposure Exposure operator ()() const { return static_cast(*this); } /// Concise version of Set(value) 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((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 struct BitsOnBase { template auto MakeField() const { return Fields{}.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 struct Fields { template static auto MakeOn(ParentClass obj) { return detail::Field{ obj }; } /// Convenience overload to allow directly passing in a this pointer template static auto MakeOn(const ParentClass* obj) { return detail::Field{ *obj }; } /// TODO: Privatize template struct MakeOn2Type { template constexpr auto operator()(ParentClass obj) const { return detail::Field{ obj }; } }; template static constexpr auto MakeOn2 = MakeOn2Type{}; }; struct OwnStoragePolicy {}; /** * Convenience class to allow for terser syntax and to automatially allocate storage. * Child classes only define the actual fields. */ template struct BitsOn : detail::BitsOnBase { // Rely on ParentClass providing a "storage" member unless ProvideStorage is true (see specialization below) }; template struct BitsOn : detail::BitsOnBase { // Automatically allocate the "storage" member Storage storage; }; } // namespace BitField } // namespace v2 namespace v3 { namespace BitField { namespace detail { template using uint_least_n_bits = std::conditional_t>>>; template 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{0})>::value; static constexpr auto field_mask = std::integral_constant> (8 * sizeof(Storage) - Bits)) << Position)>::value; static constexpr auto neg_field_mask = std::integral_constant(~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) { auto [storage] = object; if constexpr (std::is_scalar_v) { return static_cast((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>((storage & field_mask) >> Position) }; } } else { auto storage = object; // return Exposure { (storage & field_mask) >> Position }; return static_cast((storage & field_mask) >> Position); } } // Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance) constexpr ParentClass Set(Exposure value) const { //static_assert(std::is_base_of_v, "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) { auto& [storage] = ret; storage = (storage & neg_field_mask) | ((static_cast(value) << Position) & field_mask); } else { auto storage = object; ret = (storage & neg_field_mask) | ((static_cast(value) << Position) & field_mask); } return ret; } /// Concise version of casting to Exposure constexpr Exposure operator ()() const { return static_cast(*this); } /// Concise version of Set(value) 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 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 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{0})>::value; static constexpr auto field_mask = std::integral_constant> (8 * sizeof(Storage) - Bits)) << Position)>::value; static constexpr auto neg_field_mask = std::integral_constant(~field_mask)>::value; public: constexpr operator Exposure() const { return static_cast((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) ParentClass Set(Exposure value) const { return MemberLens::modify( *this, [&](const auto& data) { return (data & neg_field_mask) | ((static_cast(value) << Position) & field_mask); }); } /// Concise version of casting to Exposure constexpr Exposure operator ()() const { return static_cast(*this); } /// Concise version of Set(value) 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((Storage{1} << Bits) - 1); } // TODO: SignedBitField }; template struct PMDLens; template struct PMDLens { using MemberType = Member; template static Class modify(Class&& object, Modifier&& modifier) { // TODO: Should avoid copy for rvalue objects auto ret = std::forward(object); ret.*PointerToMember = std::forward(modifier)(ret.*PointerToMember); return ret; } template static Class modify(const Class& object, Modifier&& modifier) { auto ret = object; ret.*PointerToMember = std::forward(modifier)(ret.*PointerToMember); return ret; } static MemberType get(Class&& object) { return std::forward(object).*PointerToMember; } static MemberType get(const Class& object) { return object.*PointerToMember; } }; } // namespace detail template constexpr auto MakeFieldOn(const ParentClass* t) { if constexpr (std::is_class_v) { using Storage = decltype(detail::GetStorage(std::declval())); return detail::Field { *t }; } else { return detail::Field { *t }; } } template constexpr auto MakeFieldOn(const ParentClass* t) { if constexpr (std::is_class_v) { using Storage = decltype(detail::GetStorage(std::declval())); return detail::Field { *t }; } else { return detail::Field { *t }; } } // Usage, e.g.: auto enabled() const { return BitField::v3::MakeFlagOn< 0, &StencilTest::raw1>(this); } template::MemberType, typename ParentClass> constexpr detail::FieldNew, Exposure> MakeFieldOn(const ParentClass* t) { static_assert(std::is_class_v, "Must only call this overload on class types"); static_assert(std::is_member_pointer_v, "Given member must be a pointer-to-member"); static_assert(std::is_invocable_v, "Given object type is not compatible with the pointer-to-member"); return { *t }; } template constexpr detail::FieldNew, bool> MakeFlagOn(const ParentClass* t) { static_assert(std::is_class_v, "Must only call this overload on class types"); static_assert(std::is_member_pointer_v, "Given member must be a pointer-to-member"); static_assert(std::is_invocable_v, "Given object type is not compatible with the pointer-to-member"); return { *t }; } template constexpr auto MakeFlagOn(const ParentClass* t) { if constexpr (std::is_class_v) { using Storage = decltype(detail::GetStorage(std::declval())); return detail::Field { *t }; } else { return detail::Field { *t }; } } } // namespace BitField } // namespace v3 namespace BitField { namespace v2 { using namespace ::v2::BitField; } namespace v3 { using namespace ::v3::BitField; } }