Skip to content

Commit 4740294

Browse files
bzbarsky-applepull[bot]
authored andcommitted
Be a little more flexible about Span constructors. (#26701)
Before this change, this code: struct X { int member; }; struct Y : public X { }; Y object; Span<X> span(&Y, 1); would not compile, because we had std::is_same checks on the types (X and Y in this case). But this code is in fact safe as long as sizeof(Y) == sizeof(X), so we don't get confused about our object boundaries. This change replaces the std::is_same checks with sizeof() checks. But that leads to some situations being ambiguous, because the "can this pointer actually work for us?" check happens after overload resolution, so we explicitly do the std::is_convertible checks alongside sizeof to make sure that the pointer passed to our Span constructor will work.
1 parent 602a05a commit 4740294

File tree

2 files changed

+59
-22
lines changed

2 files changed

+59
-22
lines changed

src/lib/support/Span.h

+21-18
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ class Span
4343

4444
constexpr Span() : mDataBuf(nullptr), mDataLen(0) {}
4545
constexpr Span(pointer databuf, size_t datalen) : mDataBuf(databuf), mDataLen(datalen) {}
46-
template <size_t N>
47-
constexpr explicit Span(T (&databuf)[N]) : Span(databuf, N)
46+
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
47+
constexpr explicit Span(U (&databuf)[N]) : Span(databuf, N)
4848
{}
4949

50-
template <class U, size_t N, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
50+
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
5151
constexpr Span(std::array<U, N> & arr) : mDataBuf(arr.data()), mDataLen(N)
5252
{}
5353

@@ -60,14 +60,20 @@ class Span
6060
}
6161

6262
// Allow implicit construction from a Span over a type that matches our
63-
// type, up to const-ness.
64-
template <class U, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
63+
// type's size, if a pointer to the other type can be treated as a pointer
64+
// to our type (e.g. other type is same as ours, or is a same-size
65+
// subclass). The size check is really important to make sure we don't get
66+
// confused about where our object boundaries are.
67+
template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
6568
constexpr Span(const Span<U> & other) : Span(other.data(), other.size())
6669
{}
6770

6871
// Allow implicit construction from a FixedSpan over a type that matches our
69-
// type, up to const-ness.
70-
template <class U, size_t N, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
72+
// type's size, if a pointer to the other type can be treated as a pointer
73+
// to our type (e.g. other type is same as ours, or is a same-size
74+
// subclass). The size check is really important to make sure we don't get
75+
// confused about where our object boundaries are.
76+
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
7177
constexpr inline Span(const FixedSpan<U, N> & other);
7278

7379
constexpr pointer data() const { return mDataBuf; }
@@ -169,28 +175,25 @@ class FixedSpan
169175
//
170176
// To do that we have a template constructor enabled only when the type
171177
// passed to it is a pointer type, and that pointer is to a type that
172-
// matches T up to const-ness. Importantly, we do NOT want to allow
173-
// subclasses of T here, because they would have a different size and our
174-
// Span would not work right.
175-
template <
176-
class U,
177-
typename = std::enable_if_t<std::is_pointer<U>::value &&
178-
std::is_same<std::remove_const_t<T>, std::remove_const_t<std::remove_pointer_t<U>>>::value>>
178+
// matches T's size and can convert to T*.
179+
template <class U,
180+
typename = std::enable_if_t<std::is_pointer<U>::value && sizeof(std::remove_pointer_t<U>) == sizeof(T) &&
181+
std::is_convertible<U, T *>::value>>
179182
constexpr explicit FixedSpan(U databuf) : mDataBuf(databuf)
180183
{}
181-
template <class U, size_t M, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
184+
template <class U, size_t M, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
182185
constexpr explicit FixedSpan(U (&databuf)[M]) : mDataBuf(databuf)
183186
{
184187
static_assert(M >= N, "Passed-in buffer too small for FixedSpan");
185188
}
186189

187-
template <class U, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
190+
template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
188191
constexpr FixedSpan(std::array<U, N> & arr) : mDataBuf(arr.data())
189192
{}
190193

191194
// Allow implicit construction from a FixedSpan of sufficient size over a
192-
// type that matches our type, up to const-ness.
193-
template <class U, size_t M, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
195+
// type that has the same size as ours, as long as the pointers are convertible.
196+
template <class U, size_t M, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
194197
constexpr FixedSpan(FixedSpan<U, M> const & other) : mDataBuf(other.data())
195198
{
196199
static_assert(M >= N, "Passed-in FixedSpan is smaller than we are");

src/lib/support/tests/TestSpan.cpp

+38-4
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,48 @@ static void TestFromCharString(nlTestSuite * inSuite, void * inContext)
302302
NL_TEST_ASSERT(inSuite, s1.data_equal(CharSpan(str, 3)));
303303
}
304304

305+
static void TestConversionConstructors(nlTestSuite * inSuite, void * inContext)
306+
{
307+
struct Foo
308+
{
309+
int member = 0;
310+
};
311+
struct Bar : public Foo
312+
{
313+
};
314+
315+
Bar objects[2];
316+
317+
// Check that various things here compile.
318+
Span<Foo> span1(objects);
319+
Span<Foo> span2(&objects[0], 1);
320+
FixedSpan<Foo, 2> span3(objects);
321+
FixedSpan<Foo, 1> span4(objects);
322+
323+
Span<Bar> testSpan1(objects);
324+
FixedSpan<Bar, 2> testSpan2(objects);
325+
326+
Span<Foo> span5(testSpan1);
327+
Span<Foo> span6(testSpan2);
328+
329+
FixedSpan<Foo, 2> span7(testSpan2);
330+
}
331+
305332
#define NL_TEST_DEF_FN(fn) NL_TEST_DEF("Test " #fn, fn)
306333
/**
307334
* Test Suite. It lists all the test functions.
308335
*/
309-
static const nlTest sTests[] = { NL_TEST_DEF_FN(TestByteSpan), NL_TEST_DEF_FN(TestMutableByteSpan),
310-
NL_TEST_DEF_FN(TestFixedByteSpan), NL_TEST_DEF_FN(TestSpanOfPointers),
311-
NL_TEST_DEF_FN(TestSubSpan), NL_TEST_DEF_FN(TestFromZclString),
312-
NL_TEST_DEF_FN(TestFromCharString), NL_TEST_SENTINEL() };
336+
static const nlTest sTests[] = {
337+
NL_TEST_DEF_FN(TestByteSpan),
338+
NL_TEST_DEF_FN(TestMutableByteSpan),
339+
NL_TEST_DEF_FN(TestFixedByteSpan),
340+
NL_TEST_DEF_FN(TestSpanOfPointers),
341+
NL_TEST_DEF_FN(TestSubSpan),
342+
NL_TEST_DEF_FN(TestFromZclString),
343+
NL_TEST_DEF_FN(TestFromCharString),
344+
NL_TEST_DEF_FN(TestConversionConstructors),
345+
NL_TEST_SENTINEL(),
346+
};
313347

314348
int TestSpan()
315349
{

0 commit comments

Comments
 (0)