Skip to content

Commit

Permalink
Improve usability of spans for outparams. (#7708)
Browse files Browse the repository at this point in the history
A few changes here:

1) Allow setting the size of a Span (to a value no larger than its current size).  This enables use of a Span over a non-const type as an outparam that you fill with data and then set the size of the data.

2) Allow converting a Span<T> to a Span<const T>, so once you fill such a non-const-type Span with data you can pass it to functions expecting a Span over a const type without having to manually create one.

3) Change data_equal to allow comparing Span<T> to Span<const T>.
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Aug 31, 2021
1 parent ff48fab commit 1118072
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 3 deletions.
32 changes: 30 additions & 2 deletions src/lib/support/Span.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <cstdint>
#include <cstdlib>
#include <string.h>
#include <type_traits>

#include <support/CodeUtils.h>

Expand All @@ -44,7 +45,10 @@ class Span
constexpr pointer data() const { return mDataBuf; }
size_t size() const { return mDataLen; }
bool empty() const { return size() == 0; }
bool data_equal(const Span & other) const

// Allow data_equal for spans that are over the same type up to const-ness.
template <class U, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
bool data_equal(const Span<U> & other) const
{
return (size() == other.size()) && (empty() || (memcmp(data(), other.data(), size() * sizeof(T)) == 0));
}
Expand All @@ -56,6 +60,20 @@ class Span
return Span(mDataBuf + offset, length);
}

// Allow converting a span with non-const T into a span with const T.
template <class U = T>
operator typename std::enable_if_t<!std::is_const<U>::value, Span<const T>>() const
{
return Span<const T>(data(), size());
}

// Allow reducing the size of a span.
void reduce_size(size_t new_size)
{
VerifyOrDie(new_size <= size());
mDataLen = new_size;
}

private:
pointer mDataBuf;
size_t mDataLen;
Expand All @@ -73,12 +91,22 @@ class FixedSpan
constexpr pointer data() const { return mDataBuf; }
size_t size() const { return N; }
bool empty() const { return data() == nullptr; }
bool data_equal(const FixedSpan & other) const

// Allow data_equal for spans that are over the same type up to const-ness.
template <class U, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
bool data_equal(const FixedSpan<U, N> & other) const
{
return (empty() && other.empty()) ||
(!empty() && !other.empty() && (memcmp(data(), other.data(), size() * sizeof(T)) == 0));
}

// Allow converting a span with non-const T into a span with const T.
template <class U = T>
operator typename std::enable_if_t<!std::is_const<U>::value, FixedSpan<const T, N>>() const
{
return FixedSpan<const T, N>(data());
}

private:
pointer mDataBuf;
};
Expand Down
71 changes: 70 additions & 1 deletion src/lib/support/tests/TestSpan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,74 @@ static void TestByteSpan(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, !s5.data_equal(s4));
NL_TEST_ASSERT(inSuite, s5.data_equal(s0));
NL_TEST_ASSERT(inSuite, s0.data_equal(s5));

ByteSpan s6(arr2);
s6.reduce_size(2);
NL_TEST_ASSERT(inSuite, s6.size() == 2);
ByteSpan s7(arr2, 2);
NL_TEST_ASSERT(inSuite, s6.data_equal(s7));
NL_TEST_ASSERT(inSuite, s7.data_equal(s6));
}

static void TestMutableByteSpan(nlTestSuite * inSuite, void * inContext)
{
uint8_t arr[] = { 1, 2, 3 };

MutableByteSpan s0 = MutableByteSpan();
NL_TEST_ASSERT(inSuite, s0.data() == nullptr);
NL_TEST_ASSERT(inSuite, s0.size() == 0);
NL_TEST_ASSERT(inSuite, s0.empty());
NL_TEST_ASSERT(inSuite, s0.data_equal(s0));

MutableByteSpan s1(arr, 2);
NL_TEST_ASSERT(inSuite, s1.data() == arr);
NL_TEST_ASSERT(inSuite, s1.size() == 2);
NL_TEST_ASSERT(inSuite, !s1.empty());
NL_TEST_ASSERT(inSuite, s1.data_equal(s1));
NL_TEST_ASSERT(inSuite, !s1.data_equal(s0));

MutableByteSpan s2(arr);
NL_TEST_ASSERT(inSuite, s2.data() == arr);
NL_TEST_ASSERT(inSuite, s2.size() == 3);
NL_TEST_ASSERT(inSuite, s2.data()[2] == 3);
NL_TEST_ASSERT(inSuite, !s2.empty());
NL_TEST_ASSERT(inSuite, s2.data_equal(s2));
NL_TEST_ASSERT(inSuite, !s2.data_equal(s1));

MutableByteSpan s3 = s2;
NL_TEST_ASSERT(inSuite, s3.data() == arr);
NL_TEST_ASSERT(inSuite, s3.size() == 3);
NL_TEST_ASSERT(inSuite, s3.data()[2] == 3);
NL_TEST_ASSERT(inSuite, !s3.empty());
NL_TEST_ASSERT(inSuite, s3.data_equal(s2));

uint8_t arr2[] = { 3, 2, 1 };
MutableByteSpan s4(arr2);
NL_TEST_ASSERT(inSuite, !s4.data_equal(s2));

MutableByteSpan s5(arr2, 0);
NL_TEST_ASSERT(inSuite, s5.data() != nullptr);
NL_TEST_ASSERT(inSuite, !s5.data_equal(s4));
NL_TEST_ASSERT(inSuite, s5.data_equal(s0));
NL_TEST_ASSERT(inSuite, s0.data_equal(s5));

MutableByteSpan s6(arr2);
s6.reduce_size(2);
NL_TEST_ASSERT(inSuite, s6.size() == 2);
MutableByteSpan s7(arr2, 2);
NL_TEST_ASSERT(inSuite, s6.data_equal(s7));
NL_TEST_ASSERT(inSuite, s7.data_equal(s6));

uint8_t arr3[] = { 1, 2, 3 };
MutableByteSpan s8(arr3);
NL_TEST_ASSERT(inSuite, arr3[1] == 2);
s8.data()[1] = 3;
NL_TEST_ASSERT(inSuite, arr3[1] == 3);

// Not mutable span on purpose, to test conversion.
ByteSpan s9 = s8;
NL_TEST_ASSERT(inSuite, s9.data_equal(s8));
NL_TEST_ASSERT(inSuite, s8.data_equal(s9));
}

static void TestFixedByteSpan(nlTestSuite * inSuite, void * inContext)
Expand Down Expand Up @@ -111,7 +179,8 @@ static void TestFixedByteSpan(nlTestSuite * inSuite, void * inContext)
/**
* Test Suite. It lists all the test functions.
*/
static const nlTest sTests[] = { NL_TEST_DEF_FN(TestByteSpan), NL_TEST_DEF_FN(TestFixedByteSpan), NL_TEST_SENTINEL() };
static const nlTest sTests[] = { NL_TEST_DEF_FN(TestByteSpan), NL_TEST_DEF_FN(TestMutableByteSpan),
NL_TEST_DEF_FN(TestFixedByteSpan), NL_TEST_SENTINEL() };

int TestSpan(void)
{
Expand Down

0 comments on commit 1118072

Please sign in to comment.