From 587cf4ca9a094cdf124f3ebdcb73d9e67bd991c4 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Fri, 4 Feb 2022 10:44:48 -0600 Subject: [PATCH] Pulled latest changes to Span.h from BCHN sources Also in this commit: minor fixups to Span (and fixed a typo in VarInt.h). --- src/Span.h | 151 +++++++++++++++++++++++++++++++++++++++++++-------- src/VarInt.h | 2 +- 2 files changed, 130 insertions(+), 23 deletions(-) diff --git a/src/Span.h b/src/Span.h index 7b76de4d..b061ecc3 100644 --- a/src/Span.h +++ b/src/Span.h @@ -7,14 +7,68 @@ #include #include +#include #include #include /** 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 vec{1,2,3,4}; + * Span 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 sp(std::vector{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 arg); + * + * Foo(std::vector{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 arg); + * + * FooMut(std::vector{1, 2, 3}); // Does not compile + * std::vector 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& arg); + * FooVec(std::vector{1, 2, 3}); // Does not compile */ -template class Span { +template +class Span { C *m_data{}; std::size_t m_size{}; @@ -23,6 +77,39 @@ template 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 , 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 , int> = 0> + constexpr Span(T *begin, T *end) noexcept : Span(static_cast(begin), static_cast(end)) {} + + /** Construct a Span from an array. This matches the corresponding C++20 std::span constructor. */ + template + 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 || std::is_lvalue_reference_v) + && std::is_convertible_v().data())> (*)[], C (*)[]> + && std::is_convertible_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 @@ -63,37 +150,29 @@ template 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); } @@ -120,13 +199,41 @@ constexpr Span MakeSpan(A (&a)[N]) { return Span(a, N); } template constexpr Span MakeCSpan(const A (&a)[N]) { return Span(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 -constexpr auto MakeSpan(V &v) { - using ContainerValueType = typename std::remove_pointer_t().data())>; - return Span(v.data(), v.size()); +constexpr auto MakeSpan(V &&v) -> typename std::enable_if_t, + Span>> { + return std::forward(v); } +/** MakeSpan from any container that has .data() and .size(). For (lvalue) references, supporting mutable output. */ +template +constexpr auto MakeSpan(V &v) -> Span> { return v; } + /** Create a Span from any container that has .data() and .size() */ template 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 , char> || std::is_same_v, signed char> || std::is_same_v, unsigned char> + || std::is_same_v, int8_t> + || std::is_same_v, uint8_t> + || std::is_same_v, std::byte>, int> = 0> + auto * UInt8Cast(C * c) noexcept { + using RetType = std::conditional_t, const uint8_t, uint8_t>; + return reinterpret_cast(c); + } + + // Helper function to safely convert a Span to a Span<[const] uint8_t>. + template + auto UInt8SpanCast(Span s) -> Span> { + 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 +auto MakeUInt8Span(V &&v) -> decltype(Span_detail::UInt8SpanCast(MakeSpan(std::forward(v)))) { + return Span_detail::UInt8SpanCast(MakeSpan(std::forward(v))); +} diff --git a/src/VarInt.h b/src/VarInt.h index bcb4d198..56b261a1 100644 --- a/src/VarInt.h +++ b/src/VarInt.h @@ -129,7 +129,7 @@ class VarInt // accepted construct (takes a deep copy of bytes in span) const std::byte *spanAcceptedBytes = reinterpret_cast(const_cast(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; }