Skip to content

Commit

Permalink
Improve MemSpan const corrrectness.
Browse files Browse the repository at this point in the history
  • Loading branch information
SolidWallOfCode committed May 21, 2022
1 parent 54deb5b commit c04831a
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 35 deletions.
51 changes: 41 additions & 10 deletions code/include/swoc/MemSpan.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ namespace swoc { inline namespace SWOC_VERSION_NS {
avoid copying or allocation by allocating all needed memory at once and then working with it via
instances of this class.
@note The issue of @c const correctness is tricky. Because this class is intended as a "smart"
pointer, its constancy does not carry over to its elements, just as a constant pointer doesn't
make its target constant. This makes it different than containers such as @c std::array or
@c std::vector. This means when creating an instance based on such containers the constancy of
the container affects the element type of the span. E.g.
- A @c std::array<T,N> maps to a @c MemSpan<T>
- A @c const @c std::array<T,N> maps to a @c MemSpan<const T>
For convenience a @c MemSpan<const T> can be constructed from a @c MemSpan<T> because this maintains
@c const correctness and models how a @c T @c const* can be constructed from a @c T* .
*/
template <typename T> class MemSpan {
using self_type = MemSpan; ///< Self reference type.
Expand Down Expand Up @@ -71,14 +82,23 @@ template <typename T> class MemSpan {
* @tparam N Number of elements in the array.
* @param a The array.
*/
template <size_t N> MemSpan(T (&a)[N]);
template <auto N> MemSpan(T (&a)[N]);

/** Construct from a @c std::array.
/** Construct from constant @c std::array.
*
* @tparam N Array size.
* @param a Array instance.
*
* @note Because the elements in a constant array are constant the span value type must be constant.
*/
template <auto N> constexpr MemSpan(std::array<T, N> const& a);
template < auto N, typename U
, typename META = std::enable_if_t<
std::conjunction_v<
std::is_const<T>
, std::is_same<std::remove_const_t<U>, std::remove_const_t<T>>
>
>
> constexpr MemSpan(std::array<U, N> const& a);

/** Construct from a @c std::array.
*
Expand Down Expand Up @@ -486,17 +506,23 @@ ptr_add(void *ptr, size_t count) {
return static_cast<char *>(ptr) + count;
}

/** Functor to convert span types.
/** Meta Function to check the type compatibility of two spans..
*
* @tparam T Source span type.
* @tparam U Destination span type.
*
* The types are compatible if one is an integral multiple of the other, so the span divides evenly.
*
* @a U must not lose constancy compared to @a T.
*
* @internal More void handling. This can't go in @c MemSpan because template specialization is
* invalid in class scope and this needs to be specialized for @c void.
*/
template <typename T, typename U> struct is_span_compatible {
/// @c true if the size of @a T is an integral multiple of the size of @a U or vice versa.
static constexpr bool value = std::ratio<sizeof(T), sizeof(U)>::num == 1 || std::ratio<sizeof(U), sizeof(T)>::num == 1;
static constexpr bool value =
(std::ratio<sizeof(T), sizeof(U)>::num == 1 || std::ratio<sizeof(U), sizeof(T)>::num == 1) &&
(std::is_const_v<U> || ! std::is_const_v<T>); // can't lose constancy.
/** Compute the new size in units of @c sizeof(U).
*
* @param size Size in bytes.
Expand All @@ -521,7 +547,7 @@ is_span_compatible<T, U>::count(size_t size) {
// Must specialize for rebinding to @c void because @c sizeof doesn't work. Rebinding from @c void
// is handled by the @c MemSpan<void>::rebind specialization and doesn't use this mechanism.
template <typename T> struct is_span_compatible<T, void> {
static constexpr bool value = true;
static constexpr bool value = ! std::is_const_v<T>;
static size_t count(size_t size);
};

Expand Down Expand Up @@ -627,11 +653,11 @@ template <typename T> constexpr MemSpan<T>::MemSpan(T *ptr, size_t count) : _ptr

template <typename T> constexpr MemSpan<T>::MemSpan(T *first, T *last) : _ptr{first}, _count{detail::ptr_distance(first, last)} {}

template <typename T> template <size_t N> MemSpan<T>::MemSpan(T (&a)[N]) : _ptr{a}, _count{N} {}
template <typename T> template <auto N> MemSpan<T>::MemSpan(T (&a)[N]) : _ptr{a}, _count{N} {}

template <typename T> constexpr MemSpan<T>::MemSpan(std::nullptr_t) {}

template <typename T> template <auto N> constexpr MemSpan<T>::MemSpan(std::array<T,N> const& a) : _ptr{a.data()} , _count{a.size()} {}
template <typename T> template <auto N, typename U, typename META> constexpr MemSpan<T>::MemSpan(std::array<U,N> const& a) : _ptr{a.data()} , _count{a.size()} {}
template <typename T> template <auto N> constexpr MemSpan<T>::MemSpan(std::array<T,N> & a) : _ptr{a.data()} , _count{a.size()} {}

template <typename T>
Expand Down Expand Up @@ -808,8 +834,9 @@ template <typename U>
MemSpan<U>
MemSpan<T>::rebind() const {
static_assert(detail::is_span_compatible<T, U>::value,
"MemSpan only allows rebinding between types who sizes are integral multiples.");
return {static_cast<U *>(static_cast<void *>(_ptr)), detail::is_span_compatible<T, U>::count(this->size())};
"MemSpan only allows rebinding between types where the sizes are such that one is an integral multiple of the other.");
using VOID_PTR = std::conditional_t<std::is_const_v<U>, const void *, void*>;
return {static_cast<U *>(static_cast<VOID_PTR>(_ptr)), detail::is_span_compatible<T, U>::count(this->size())};
}

template <typename T>
Expand Down Expand Up @@ -954,6 +981,10 @@ MemSpan<void>::view() const {
return {static_cast<char const *>(_ptr), _size};
}

/// Deduction guide for constructing from a @c std::array.
template<typename T, size_t N> MemSpan(std::array<T,N> &) -> MemSpan<T>;
template<typename T, size_t N> MemSpan(std::array<T,N> const &) -> MemSpan<T const>;

}} // namespace swoc::SWOC_VERSION_NS

/// @cond NO_DOXYGEN
Expand Down
84 changes: 59 additions & 25 deletions code/include/swoc/swoc_ip.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ class IP4Addr {
public:
static constexpr size_t SIZE = sizeof(in_addr_t); ///< Size of IPv4 address in bytes.
static constexpr size_t WIDTH = std::numeric_limits<unsigned char>::digits * SIZE; ///< # of bits in an address.

static const self_type MIN; ///< Minimum value.
static const self_type MAX; ///< Maximum value.
static constexpr sa_family_t AF_value = AF_INET; ///< Address family type.
Expand Down Expand Up @@ -350,8 +351,8 @@ class IP4Addr {
*/
class IP6Addr {
using self_type = IP6Addr; ///< Self reference type.
friend class IP6Range;

friend class IP6Range;
friend class IPMask;

public:
Expand All @@ -361,35 +362,13 @@ class IP6Addr {

using quad_type = uint16_t; ///< Size of one segment of an IPv6 address.
static constexpr size_t N_QUADS = SIZE / sizeof(quad_type); ///< # of quads in an IPv6 address.
/// Number of bits per quad.
static constexpr size_t QUAD_WIDTH = std::numeric_limits<uint8_t>::digits * sizeof(quad_type);

/// Direct access type for the address.
/// Equivalent to the data type for data member @c s6_addr in @c in6_addr.
using raw_type = std::array<uint8_t, SIZE>;

/// Direct access type for the address by quads (16 bits).
/// This corresponds to the elements of the text format of the address.
using quad_store_type = std::array<quad_type, N_QUADS>;

/// Number of bits per quad.
static constexpr size_t QUAD_WIDTH = std::numeric_limits<uint8_t>::digits * sizeof(quad_type);

/// A bit mask of all 1 bits the size of a quad.
static constexpr quad_type QUAD_MASK = ~quad_type{0};

/// Type used as a "word", the natural working unit of the address.
using word_type = uint64_t;

static constexpr size_t WORD_SIZE = sizeof(word_type);

/// Number of bits per word.
static constexpr size_t WORD_WIDTH = std::numeric_limits<uint8_t>::digits * WORD_SIZE;

/// Number of words used for basic address storage.
static constexpr size_t N_STORE = SIZE / WORD_SIZE;

/// Type used to store the address.
using word_store_type = std::array<word_type, N_STORE>;

/// Minimum value of an address.
static const self_type MIN;
/// Maximum value of an address.
Expand Down Expand Up @@ -531,6 +510,14 @@ class IP6Addr {
*/
static void reorder(raw_type &dst, in6_addr const &src);

template < typename T > auto as_span() -> std::enable_if_t<swoc::meta::is_any_of_v<T, std::byte, uint8_t, uint16_t, uint32_t, uint64_t>, swoc::MemSpan<T>> {
return swoc::MemSpan(_addr._store).template rebind<T>();
}

template < typename T > auto as_span() const -> std::enable_if_t<swoc::meta::is_any_of_v<typename std::remove_const_t<T>, std::byte, uint8_t, uint16_t, uint32_t, uint64_t>, swoc::MemSpan<T const>> {
return swoc::MemSpan<uint64_t const>(_addr._store).template rebind<T const>();
}

protected:
friend bool operator==(self_type const &, self_type const &);

Expand All @@ -540,6 +527,27 @@ class IP6Addr {

friend bool operator<=(self_type const &, self_type const &);

/// Direct access type for the address by quads (16 bits).
/// This corresponds to the elements of the text format of the address.
using quad_store_type = std::array<quad_type, N_QUADS>;

/// A bit mask of all 1 bits the size of a quad.
static constexpr quad_type QUAD_MASK = ~quad_type{0};

/// Type used as a "word", the natural working unit of the address.
using word_type = uint64_t;

static constexpr size_t WORD_SIZE = sizeof(word_type);

/// Number of bits per word.
static constexpr size_t WORD_WIDTH = std::numeric_limits<uint8_t>::digits * WORD_SIZE;

/// Number of words used for basic address storage.
static constexpr size_t N_STORE = SIZE / WORD_SIZE;

/// Type used to store the address.
using word_store_type = std::array<word_type, N_STORE>;

/// Type for digging around inside the address, with the various forms of access.
/// These are in sort of host order - @a _store elements are host order, but the
/// MSW and LSW are swapped (big-endian). This makes various bits of the implementation
Expand Down Expand Up @@ -3339,3 +3347,29 @@ get(swoc::IPNet const &net) {
}

}} // namespace swoc::SWOC_VERSION_NS

namespace std {
template <> struct hash<swoc::IP4Addr> {
uint32_t operator()(swoc::IP4Addr const &addr) const {
return addr.network_order();
}
};

template <> struct hash<swoc::IP6Addr> {
uint32_t operator()(swoc::IP6Addr const &addr) const {
// XOR the 64 chunks then XOR that down to 32 bits.
auto words = addr.as_span<uint64_t>();
union {
uint64_t w;
uint32_t n[2];
} x{words[0] ^ words[1]};
return x.n[0] ^ x.n[1];
}
};

template <> struct hash<swoc::IPAddr> {
uint32_t operator()(swoc::IPAddr const &addr) const {
return addr.is_ip4() ? hash<swoc::IP4Addr>()(addr.ip4()) : addr.is_ip6() ? hash<swoc::IP6Addr>()(addr.ip6()) : 0;
}
};
} // namespace std
16 changes: 16 additions & 0 deletions unit_tests/test_MemSpan.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,19 @@ TEST_CASE("MemSpan<void>", "[libswoc][MemSpan]")
REQUIRE(left.size() + span.size() == 1024);

};

TEST_CASE("MemSpan conversions", "[libswoc][MemSpan]")
{
std::array<int, 10> a1;
auto const & ra1 = a1;
auto ms1 = MemSpan<int>(a1);
[[maybe_unused]] auto ms2 = MemSpan(a1);
[[maybe_unused]] auto ms3 = MemSpan<int const>(ra1);
[[maybe_unused]] auto ms4 = MemSpan(ra1);
// Construct a span of constant from a const ref to an array with non-const type.
MemSpan<const int> ms5 { ra1 };
// Construct a span of constant from a ref to an array with non-const type.
MemSpan<const int> ms6 { a1 };

[[maybe_unused]] MemSpan<int const> c1 = ms1; // Conversion from T to T const.
}

0 comments on commit c04831a

Please sign in to comment.