Skip to content

Commit

Permalink
Pulled latest changes to Span.h from BCHN sources
Browse files Browse the repository at this point in the history
Also in this commit: minor fixups to Span (and fixed a typo in
VarInt.h).
  • Loading branch information
cculianu committed Feb 4, 2022
1 parent 43aa064 commit 587cf4c
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 23 deletions.
151 changes: 129 additions & 22 deletions src/Span.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,68 @@

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <type_traits>

/** A Span is an object that can refer to a contiguous sequence of objects.
*
* It implements a subset of C++20's std::span.
*
* Things to be aware of when writing code that deals with Spans:
*
* - Similar to references themselves, Spans are subject to reference lifetime
* issues. The user is responsible for making sure the objects pointed to by
* a Span live as long as the Span is used. For example:
*
* std::vector<int> vec{1,2,3,4};
* Span<int> sp(vec);
* vec.push_back(5);
* printf("%i\n", sp.front()); // UB!
*
* may exhibit undefined behavior, as increasing the size of a vector may
* invalidate references.
*
* - One particular pitfall is that Spans can be constructed from temporaries,
* but this is unsafe when the Span is stored in a variable, outliving the
* temporary. For example, this will compile, but exhibits undefined behavior:
*
* Span<const int> sp(std::vector<int>{1, 2, 3});
* printf("%i\n", sp.front()); // UB!
*
* The lifetime of the vector ends when the statement it is created in ends.
* Thus the Span is left with a dangling reference, and using it is undefined.
*
* - Due to Span's automatic creation from range-like objects (arrays, and data
* types that expose a data() and size() member function), functions that
* accept a Span as input parameter can be called with any compatible
* range-like object. For example, this works:
*
* void Foo(Span<const int> arg);
*
* Foo(std::vector<int>{1, 2, 3}); // Works
*
* This is very useful in cases where a function truly does not care about the
* container, and only about having exactly a range of elements. However it
* may also be surprising to see automatic conversions in this case.
*
* When a function accepts a Span with a mutable element type, it will not
* accept temporaries; only variables or other references. For example:
*
* void FooMut(Span<int> arg);
*
* FooMut(std::vector<int>{1, 2, 3}); // Does not compile
* std::vector<int> baz{1, 2, 3};
* FooMut(baz); // Works
*
* This is similar to how functions that take (non-const) lvalue references
* as input cannot accept temporaries. This does not work either:
*
* void FooVec(std::vector<int>& arg);
* FooVec(std::vector<int>{1, 2, 3}); // Does not compile
*/
template <typename C> class Span {
template <typename C>
class Span {
C *m_data{};
std::size_t m_size{};

Expand All @@ -23,6 +77,39 @@ template <typename C> class Span {
constexpr Span(C *data, std::size_t size) noexcept : m_data(data), m_size(size) {}
constexpr Span(C *data, C *end) noexcept : m_data(data), m_size(end >= data ? end - data : 0) {}

/** Construct a span from a begin pointer and a size.
*
* This implements a subset of the iterator-based std::span constructor in C++20,
* which is hard to implement without std::address_of.
*/
template <typename T, typename std::enable_if_t<std::is_convertible_v<T (*)[], C (*)[]>, int> = 0>
constexpr Span(T *begin, std::size_t size) noexcept : m_data(begin), m_size(size) {}

/** Construct a span from a begin and end pointer.
*
* This implements a subset of the iterator-based std::span constructor in C++20,
* which is hard to implement without std::address_of.
*/
template <typename T, typename std::enable_if_t<std::is_convertible_v<T (*)[], C (*)[]>, int> = 0>
constexpr Span(T *begin, T *end) noexcept : Span(static_cast<C *>(begin), static_cast<C *>(end)) {}

/** Construct a Span from an array. This matches the corresponding C++20 std::span constructor. */
template <std::size_t N>
constexpr Span(C (&a)[N]) noexcept : m_data(a), m_size(N) {}

/** Construct a Span for objects with .data() and .size() (std::string, std::array, std::vector, ...).
*
* This implements a subset of the functionality provided by the C++20 std::span range-based constructor.
*
* To prevent surprises, only Spans for constant value types are supported when passing in temporaries.
* Note that this restriction does not exist when converting arrays or other Spans (see above).
*/
template <typename V,
typename std::enable_if_t<(std::is_const_v<C> || std::is_lvalue_reference_v<V>)
&& std::is_convertible_v<typename std::remove_pointer_t<decltype(std::declval<V>().data())> (*)[], C (*)[]>
&& std::is_convertible_v<decltype(std::declval<V>().size()), std::size_t>, int> = 0>
constexpr Span(V &&v) noexcept : m_data(v.data()), m_size(v.size()) {}

/** Implicit conversion of spans between compatible types.
*
* Specifically, if a pointer to an array of type O can be implicitly converted to a pointer to an array of type
Expand Down Expand Up @@ -63,37 +150,29 @@ template <typename C> class Span {
}

/** Pop the last element off, and return a reference to that element.
Span must not be empty(); span will decrease in size by 1, having its end() moved back by 1.
Throws std::out_of_range if the span was empty. */
constexpr C & pop_back() {
if (empty()) throw std::out_of_range("Span pop_back failed: empty");
return m_data[--m_size];
}
Span must not be empty(); span will decrease in size by 1, having its end() moved back by 1. */
constexpr C & pop_back() noexcept { return m_data[--m_size]; }

/** Pop the last element off, and return a reference to that element.
Span must not be empty(); span will decrease in size by 1, having its begin() moved up by 1. */
constexpr C & pop_front() {
if (empty()) throw std::out_of_range("Span pop_back failed: empty");
--m_size;
return *m_data++;
}
constexpr C & pop_front() noexcept { --m_size; return *m_data++; }

friend constexpr bool operator==(const Span &a, const Span &b) noexcept {
friend bool operator==(const Span &a, const Span &b) noexcept {
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
}
friend constexpr bool operator!=(const Span &a, const Span &b) noexcept {
friend bool operator!=(const Span &a, const Span &b) noexcept {
return !(a == b);
}
friend constexpr bool operator<(const Span &a, const Span &b) noexcept {
friend bool operator<(const Span &a, const Span &b) noexcept {
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
}
friend constexpr bool operator<=(const Span &a, const Span &b) noexcept {
friend bool operator<=(const Span &a, const Span &b) noexcept {
return !(b < a);
}
friend constexpr bool operator>(const Span &a, const Span &b) noexcept {
friend bool operator>(const Span &a, const Span &b) noexcept {
return b < a;
}
friend constexpr bool operator>=(const Span &a, const Span &b) noexcept {
friend bool operator>=(const Span &a, const Span &b) noexcept {
return !(a < b);
}

Expand All @@ -120,13 +199,41 @@ constexpr Span<A> MakeSpan(A (&a)[N]) { return Span<A>(a, N); }
template <typename A, std::size_t N>
constexpr Span<A> MakeCSpan(const A (&a)[N]) { return Span<const A>(a, N); }

/** Create a Span from any container that has .data() and .size() */
/** MakeSpan from any container that has .data() and .size(). For temporaries / rvalue references, only supporting const output. */
template <typename V>
constexpr auto MakeSpan(V &v) {
using ContainerValueType = typename std::remove_pointer_t<decltype(std::declval<V>().data())>;
return Span<ContainerValueType>(v.data(), v.size());
constexpr auto MakeSpan(V &&v) -> typename std::enable_if_t<!std::is_lvalue_reference_v<V>,
Span<const typename std::remove_pointer_t<decltype(v.data())>>> {
return std::forward<V>(v);
}

/** MakeSpan from any container that has .data() and .size(). For (lvalue) references, supporting mutable output. */
template <typename V>
constexpr auto MakeSpan(V &v) -> Span<typename std::remove_pointer_t<decltype(v.data())>> { return v; }

/** Create a Span<const value_type> from any container that has .data() and .size() */
template <typename V>
constexpr auto MakeCSpan(const V &v) { return MakeSpan(v); }

namespace Span_detail {
// Helper function to safely cast to uint8_t pointers from any character-like pointer.
template <typename C, typename std::enable_if_t<std::is_same_v<std::remove_const_t<C>, char> || std::is_same_v<std::remove_const_t<C>, signed char> || std::is_same_v<std::remove_const_t<C>, unsigned char>
|| std::is_same_v<std::remove_const_t<C>, int8_t>
|| std::is_same_v<std::remove_const_t<C>, uint8_t>
|| std::is_same_v<std::remove_const_t<C>, std::byte>, int> = 0>
auto * UInt8Cast(C * c) noexcept {
using RetType = std::conditional_t<std::is_const_v<C>, const uint8_t, uint8_t>;
return reinterpret_cast<RetType *>(c);
}

// Helper function to safely convert a Span<any_char_type> to a Span<[const] uint8_t>.
template <typename T>
auto UInt8SpanCast(Span<T> s) -> Span<typename std::remove_pointer_t<decltype(UInt8Cast(s.data()))>> {
return {UInt8Cast(s.data()), s.size()};
}
} // namespace Span_detail

/** Like MakeSpan, but for [const] uint8_t member types only. Only works for character/byte containers. */
template <typename V>
auto MakeUInt8Span(V &&v) -> decltype(Span_detail::UInt8SpanCast(MakeSpan(std::forward<V>(v)))) {
return Span_detail::UInt8SpanCast(MakeSpan(std::forward<V>(v)));
}
2 changes: 1 addition & 1 deletion src/VarInt.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class VarInt
// accepted construct (takes a deep copy of bytes in span)
const std::byte *spanAcceptedBytes = reinterpret_cast<const std::byte *>(const_cast<const Byte *>(b.data()));
VarInt ret(Unchecked{}, ByteView{spanAcceptedBytes, byteLen});
b = b.subspan(byteLen); // uodate b (consume bytes)
b = b.subspan(byteLen); // update b (consume bytes)
return ret;
}

Expand Down

0 comments on commit 587cf4c

Please sign in to comment.