Skip to content

Commit fadccc2

Browse files
author
MarcoFalke
committed
refactor: Make Span an alias of std::span
This uses a macro, which can be a bit more brittle than an alias template. However, class template argument deduction for alias templates is only implemented in clang-19.
1 parent fa27e36 commit fadccc2

File tree

3 files changed

+3
-182
lines changed

3 files changed

+3
-182
lines changed

doc/developer-notes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,14 +856,14 @@ class A
856856
- *Rationale*: Easier to understand what is happening, thus easier to spot mistakes, even for those
857857
that are not language lawyers.
858858
859-
- Use `Span` as function argument when it can operate on any range-like container.
859+
- Use `std::span` as function argument when it can operate on any range-like container.
860860
861861
- *Rationale*: Compared to `Foo(const vector<int>&)` this avoids the need for a (potentially expensive)
862862
conversion to vector if the caller happens to have the input stored in another type of container.
863863
However, be aware of the pitfalls documented in [span.h](../src/span.h).
864864
865865
```cpp
866-
void Foo(Span<const int> data);
866+
void Foo(std::span<const int> data);
867867
868868
std::vector<int> vec{1,2,3};
869869
Foo(vec);

src/span.h

Lines changed: 1 addition & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,7 @@
1111
#include <type_traits>
1212
#include <utility>
1313

14-
#ifdef DEBUG
15-
#define CONSTEXPR_IF_NOT_DEBUG
16-
#define ASSERT_IF_DEBUG(x) assert((x))
17-
#else
18-
#define CONSTEXPR_IF_NOT_DEBUG constexpr
19-
#define ASSERT_IF_DEBUG(x)
20-
#endif
21-
22-
#if defined(__clang__)
23-
#if __has_attribute(lifetimebound)
24-
#define SPAN_ATTR_LIFETIMEBOUND [[clang::lifetimebound]]
25-
#else
26-
#define SPAN_ATTR_LIFETIMEBOUND
27-
#endif
28-
#else
29-
#define SPAN_ATTR_LIFETIMEBOUND
30-
#endif
31-
3214
/** A Span is an object that can refer to a contiguous sequence of objects.
33-
*
34-
* This file implements a subset of C++20's std::span. It can be considered
35-
* temporary compatibility code until C++20 and is designed to be a
36-
* self-contained abstraction without depending on other project files. For this
37-
* reason, Clang lifetimebound is defined here instead of including
38-
* <attributes.h>, which also defines it.
3915
*
4016
* Things to be aware of when writing code that deals with Spans:
4117
*
@@ -93,155 +69,7 @@
9369
* result will be present in that variable after the call. Passing a temporary
9470
* is useless in that context.
9571
*/
96-
template<typename C>
97-
class Span
98-
{
99-
C* m_data;
100-
std::size_t m_size{0};
101-
102-
template <class T>
103-
struct is_Span_int : public std::false_type {};
104-
template <class T>
105-
struct is_Span_int<Span<T>> : public std::true_type {};
106-
template <class T>
107-
struct is_Span : public is_Span_int<typename std::remove_cv<T>::type>{};
108-
109-
110-
public:
111-
constexpr Span() noexcept : m_data(nullptr) {}
112-
113-
/** Construct a span from a begin pointer and a size.
114-
*
115-
* This implements a subset of the iterator-based std::span constructor in C++20,
116-
* which is hard to implement without std::address_of.
117-
*/
118-
template <typename T, typename std::enable_if<std::is_convertible<T (*)[], C (*)[]>::value, int>::type = 0>
119-
constexpr Span(T* begin, std::size_t size) noexcept : m_data(begin), m_size(size) {}
120-
121-
/** Construct a span from a begin and end pointer.
122-
*
123-
* This implements a subset of the iterator-based std::span constructor in C++20,
124-
* which is hard to implement without std::address_of.
125-
*/
126-
template <typename T, typename std::enable_if<std::is_convertible<T (*)[], C (*)[]>::value, int>::type = 0>
127-
CONSTEXPR_IF_NOT_DEBUG Span(T* begin, T* end) noexcept : m_data(begin), m_size(end - begin)
128-
{
129-
ASSERT_IF_DEBUG(end >= begin);
130-
}
131-
132-
/** Implicit conversion of spans between compatible types.
133-
*
134-
* Specifically, if a pointer to an array of type O can be implicitly converted to a pointer to an array of type
135-
* C, then permit implicit conversion of Span<O> to Span<C>. This matches the behavior of the corresponding
136-
* C++20 std::span constructor.
137-
*
138-
* For example this means that a Span<T> can be converted into a Span<const T>.
139-
*/
140-
template <typename O, typename std::enable_if<std::is_convertible<O (*)[], C (*)[]>::value, int>::type = 0>
141-
constexpr Span(const Span<O>& other) noexcept : m_data(other.m_data), m_size(other.m_size) {}
142-
143-
/** Default copy constructor. */
144-
constexpr Span(const Span&) noexcept = default;
145-
146-
/** Default assignment operator. */
147-
Span& operator=(const Span& other) noexcept = default;
148-
149-
/** Construct a Span from an array. This matches the corresponding C++20 std::span constructor. */
150-
template <int N>
151-
constexpr Span(C (&a)[N]) noexcept : m_data(a), m_size(N) {}
152-
153-
/** Construct a Span for objects with .data() and .size() (std::string, std::array, std::vector, ...).
154-
*
155-
* This implements a subset of the functionality provided by the C++20 std::span range-based constructor.
156-
*
157-
* To prevent surprises, only Spans for constant value types are supported when passing in temporaries.
158-
* Note that this restriction does not exist when converting arrays or other Spans (see above).
159-
*/
160-
template <typename V>
161-
constexpr Span(V& other SPAN_ATTR_LIFETIMEBOUND,
162-
typename std::enable_if<!is_Span<V>::value &&
163-
std::is_convertible<typename std::remove_pointer<decltype(std::declval<V&>().data())>::type (*)[], C (*)[]>::value &&
164-
std::is_convertible<decltype(std::declval<V&>().size()), std::size_t>::value, std::nullptr_t>::type = nullptr)
165-
: m_data(other.data()), m_size(other.size()){}
166-
167-
template <typename V>
168-
constexpr Span(const V& other SPAN_ATTR_LIFETIMEBOUND,
169-
typename std::enable_if<!is_Span<V>::value &&
170-
std::is_convertible<typename std::remove_pointer<decltype(std::declval<const V&>().data())>::type (*)[], C (*)[]>::value &&
171-
std::is_convertible<decltype(std::declval<const V&>().size()), std::size_t>::value, std::nullptr_t>::type = nullptr)
172-
: m_data(other.data()), m_size(other.size()){}
173-
174-
constexpr C* data() const noexcept { return m_data; }
175-
constexpr C* begin() const noexcept { return m_data; }
176-
constexpr C* end() const noexcept { return m_data + m_size; }
177-
CONSTEXPR_IF_NOT_DEBUG C& front() const noexcept
178-
{
179-
ASSERT_IF_DEBUG(size() > 0);
180-
return m_data[0];
181-
}
182-
CONSTEXPR_IF_NOT_DEBUG C& back() const noexcept
183-
{
184-
ASSERT_IF_DEBUG(size() > 0);
185-
return m_data[m_size - 1];
186-
}
187-
constexpr std::size_t size() const noexcept { return m_size; }
188-
constexpr std::size_t size_bytes() const noexcept { return sizeof(C) * m_size; }
189-
constexpr bool empty() const noexcept { return size() == 0; }
190-
CONSTEXPR_IF_NOT_DEBUG C& operator[](std::size_t pos) const noexcept
191-
{
192-
ASSERT_IF_DEBUG(size() > pos);
193-
return m_data[pos];
194-
}
195-
CONSTEXPR_IF_NOT_DEBUG Span<C> subspan(std::size_t offset) const noexcept
196-
{
197-
ASSERT_IF_DEBUG(size() >= offset);
198-
return Span<C>(m_data + offset, m_size - offset);
199-
}
200-
CONSTEXPR_IF_NOT_DEBUG Span<C> subspan(std::size_t offset, std::size_t count) const noexcept
201-
{
202-
ASSERT_IF_DEBUG(size() >= offset + count);
203-
return Span<C>(m_data + offset, count);
204-
}
205-
CONSTEXPR_IF_NOT_DEBUG Span<C> first(std::size_t count) const noexcept
206-
{
207-
ASSERT_IF_DEBUG(size() >= count);
208-
return Span<C>(m_data, count);
209-
}
210-
CONSTEXPR_IF_NOT_DEBUG Span<C> last(std::size_t count) const noexcept
211-
{
212-
ASSERT_IF_DEBUG(size() >= count);
213-
return Span<C>(m_data + m_size - count, count);
214-
}
215-
216-
template <typename O> friend class Span;
217-
};
218-
219-
// Return result of calling .data() method on type T. This is used to be able to
220-
// write template deduction guides for the single-parameter Span constructor
221-
// below that will work if the value that is passed has a .data() method, and if
222-
// the data method does not return a void pointer.
223-
//
224-
// It is important to check for the void type specifically below, so the
225-
// deduction guides can be used in SFINAE contexts to check whether objects can
226-
// be converted to spans. If the deduction guides did not explicitly check for
227-
// void, and an object was passed that returned void* from data (like
228-
// std::vector<bool>), the template deduction would succeed, but the Span<void>
229-
// object instantiation would fail, resulting in a hard error, rather than a
230-
// SFINAE error.
231-
// https://stackoverflow.com/questions/68759148/sfinae-to-detect-the-explicitness-of-a-ctad-deduction-guide
232-
// https://stackoverflow.com/questions/16568986/what-happens-when-you-call-data-on-a-stdvectorbool
233-
template<typename T>
234-
using DataResult = std::remove_pointer_t<decltype(std::declval<T&>().data())>;
235-
236-
// Deduction guides for Span
237-
// For the pointer/size based and iterator based constructor:
238-
template <typename T, typename EndOrSize> Span(T*, EndOrSize) -> Span<T>;
239-
// For the array constructor:
240-
template <typename T, std::size_t N> Span(T (&)[N]) -> Span<T>;
241-
// For the temporaries/rvalue references constructor, only supporting const output.
242-
template <typename T> Span(T&&) -> Span<std::enable_if_t<!std::is_lvalue_reference_v<T> && !std::is_void_v<DataResult<T&&>>, const DataResult<T&&>>>;
243-
// For (lvalue) references, supporting mutable output.
244-
template <typename T> Span(T&) -> Span<std::enable_if_t<!std::is_void_v<DataResult<T&>>, DataResult<T&>>>;
72+
#define Span std::span
24573

24674
/** Pop the last element off a span, and return a reference to that element. */
24775
template <typename T>

src/test/span_tests.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,6 @@ BOOST_AUTO_TEST_SUITE(span_tests)
4747
// don't work. This makes it is possible to use the Span constructor in a SFINAE
4848
// contexts like in the Spannable function above to detect whether types are or
4949
// aren't compatible with Spans at compile time.
50-
//
51-
// Previously there was a bug where writing a SFINAE check for vector<bool> was
52-
// not possible, because in libstdc++ vector<bool> has a data() member
53-
// returning void, and the Span template guide ignored the data() return value,
54-
// so the template substitution would succeed, but the constructor would fail,
55-
// resulting in a fatal compile error, rather than a SFINAE error that could be
56-
// handled.
5750
BOOST_AUTO_TEST_CASE(span_constructor_sfinae)
5851
{
5952
BOOST_CHECK(Spannable(std::vector<int>{}));

0 commit comments

Comments
 (0)