-
Notifications
You must be signed in to change notification settings - Fork 3k
Platform: Add C++ Span class. #7828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The Span class allows the creation of views over contiguous memory. The view do not own memory, is typed and has a length. It can be used as a replacement of the traditional pair of pointer and size in parameters or class fields. Main operations: - size(): return the lenght of the memory viewed - empty(): return if the memory viewed is empty - [index]: access elements viewed - data(): return a pointer to the memory viewed. - first(count): Create a subview from the first count elements. - last(count): Create a subview from the last count elements. - == and !=: compare two views or a view to array and return if they are equal or not. The Span class came in two flavors: - Static size: The size is encoded in the Span type and it is as lightweitgh as a single pointer, - Dynamic size: The object can store arbitrary views and it costs one pointer and the size of the view.
@donatieng @paul-szczepanek-arm This is an improved version of the |
why not update array view? do we really need both of them? |
Two functions have been added: static const char hello[] = "Hello World!";
bool is_greeting(const Span<uint8_t>& v) {
return v == hello;
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, apart from editing nits. I've been advocating string_view
for a while (mainly to client guys) - not sure if this effectively fully covers that, or it would need a few more implicit conversions.
platform/Span.h
Outdated
template<typename T, ptrdiff_t Size = SPAN_DYNAMIC_SIZE> | ||
struct Span { | ||
|
||
MBED_STATIC_ASSERT(Size >= 0, "Invalid size for an ArrayView"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Search-and-replace fail on the name.
platform/Span.h
Outdated
*/ | ||
bool empty() const | ||
{ | ||
return size() ? false : true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird implicit boolean inside ternary operator. Surely size() == 0
?
platform/Span.h
Outdated
* | ||
* @return The raw pointer to the array. | ||
*/ | ||
const T* data() const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting rules say pushing the *&
to the right.
platform/Span.h
Outdated
* @param array_size Number of elements of T present in the array. | ||
* | ||
* @post a call to size() will return array_size and data() will return | ||
* array_tpr. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(@p
)? array_ptr
platform/Span.h
Outdated
* If the type use this value then the size of the array is stored in the object | ||
* at runtime. | ||
*/ | ||
#define SPAN_DYNAMIC_SIZE -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for more naming consistency with C++, should the the template parameter and this constant be "extent" rather than "size"?
Feels a bit wonky, but I guess it's intended to allow you to separately query extent
from size
in template nonsense. (if (span.extent == std::dynamic_extent) ...
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will fix the name of the dynamic tag however it is not meant to be queried at runtime as this information is not really useful.
Span with fixed extent are mostly useful for type(def) that requires a fixed size otherwise the dynamic extent should be used. Also, the conversion rules offer implicit conversion from Span<T, Size>
to Span<T>
so users don't have to worry about conversions.
Thanks @kjbracey-arm ; I've updated my code according to your review. To have a I think it could be easy to add find functions as free functions targeting Span; all other aspects of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few more review notes.
platform/Span.h
Outdated
* SPAN_DYNAMIC_SIZE is special as it allows construction of Span objects of | ||
* any size (set at runtime). | ||
*/ | ||
template<typename T, ptrdiff_t Size = SPAN_DYNAMIC_EXTENT> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In C++ the template parameter is also Extent
.
platform/Span.h
Outdated
* @param array_ptr Pointer to the array data | ||
* @param array_size Number of elements of T present in the array. | ||
* | ||
* @post a call to size() will return array_size and data() will return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not true - you'll return Size
, not array_size
.
C++ restricts this constructor to matching size, making the difference only visible in undefined behaviour. Tighten the assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure to understand why this is still up has it has been fixed in 3985fb8
platform/Span.h
Outdated
*/ | ||
size_t size() const | ||
{ | ||
return _array ? Size : 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need a static span to have the runtime option of being empty? C++ only permits empty for Extent==0
or dynamic spans. From a compile-time correctness view, I quite like the idea of enforcing non-0 static spans to be non-null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is valuable to not enforce non-0 static spans to be non-null as it can be use to represent the absence of values. That's useful for optional values represented by a static span.
struct pdu_t {
// options have a fixed size
typedef Span<uint8_t, 5> option_t;
uint8_t header;
option_t option;
Span<uint8_t> payload;
};
void process_pdu(const pdu_t& pdu) {
if (!pdu.option.empty()) {
// process options ...
}
}
Note that it raises the question of operator bool() const
. It is not part of std::span
for consistency with the standard library (see 1 and 2); I believe that is up to debate in our use case; but maybe we can have this debate once this class gets in and have more feedbacks from users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ta for links. I can see it's useful, much as allowing references to be null would be useful, but it is a major deviation from the standard C++ equivalent, where that syntax guarantees there are 5 uint8_t
s.
I believe the C++ standard guys would tell you to use optional<span>
, but then they're not too bothered about doubling memory usage by adding an extra bool
alongside a pointer. In the absence of optional
, and to save space, maybe you could designate the extended type OptionalSpan
to make clear the extended semantics, although it becomes less catchy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we ever have an optional type (would you be interested by one ? ), we can have a template specialization for Span
that only stores the Span
and look at data()
to determine if the array is present or not.
The issue with consistency is that depending on the stand point something can be declared consistent or not given that the language is full of inconsistencies.
An array naturally decays to a pointer in pointer/boolean context so from that perspective it would be consistent to have the operator bool() const
member function. At the same time, standard/gsl members consider that span
should be treated as a regular container and none of the container exposes that operator: they do expose an empty()
member function to test if the container is empty or not.
To go back, to the initial issue you raised, in std::span
, it is perfectly fine to store nothing in fixed size span. Should the code in this PR change to not accept empty span of non-0 size ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree that the language is utterly full of inconsistencies, and it's alarming that we see stuff like string_view
->span
being thrashed out in consecutive standards that are (from our point of view) 3-4 standards in the future. string_view
being semi-obsolete before we ever get it is just daft.
But whenever they do manage to pin something down, if we're going to copy its name I think we should copy the full semantics, just to not add to the confusion.
I'm also with Herb Sutter on the importance of distinguishing "maybe NULL" and "not-NULL" in the language.
In part that's because I'm aware that if the language could enforce non-NULL for pointers we could get rid of hundreds of runtime checks for NULL that get inserted to cope with unit tests that try passing NULL (sometimes as their only "test"). Unit tests may not believe in argument preconditions, but if we can stop them compiling...
So when I see that the standard has left room for that distinction, I'd like to have the chance to use it. In this form, the PR leaves no nice namespace room for the alternative "non-NULL" semantics. I'd like to reserve the name Span
for that, with the alternative being OptionalSpan
or equivalent. Then whether this PR actually adds Span
or OptionalSpan
or both is up to you - whatever you think you currently need?
And OptionalSpan
could certainly include bool()
as optional<span>
does.
Indeed! The client guys do have an internal It seems your implementation does have helpers like the array<->span comparison that C++ lacks - C++ only has those for |
Standard C++ defines standard comparison operator for spans however it does not define helpers that auto convert arrays into span: uint8_t foo[] = "hello";
// works
if (mbed::Span<uint8_t>(foo) == foo) { }
// error
if (std::span<uint8_t>(foo) == foo) { }
// works
if (std::span<uint8_t>(foo) == std::span<uint8_t>(foo)) { } |
This commit aims to make Span implementation more in line with what is present in N4762: - use appropiate index types where applicable. - use typedefed type inside the class (index_type, reference, pointer, element_type) - assertion where applicable - restrict default construction to Span with extent == 0 or extent == dynamic. - construct span from a range of pointer - remove non const overload of the subscript operator - remove non const overload of the data function - implement subspan function - implement missing first and last function of dynamic span
@kjbracey-arm following our offline discussion, I've updated the implementation. to be more in line with the latest standard draft. Apologies if I haven't split it into many commits :(. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks a lot better to me. Just editing issues remain, I think.
platform/Span.h
Outdated
* Extent != 0 . | ||
*/ | ||
Span() : _data(NULL) { | ||
MBED_STATIC_ASSERT(Extent == 0, "Invalid extent for a Span"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shame we can't kill the overload suppress. Anyway, this isn't the correct error - should be something like "Cannot default construct a static-extent Span (unless Extent is 0)". Make that less wordy if you can...
Mind you, suppressing the overload would lead to a less-clear error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible to kill the overload (see here) but if I can stay away of enable_if and other TMP tricks I do as others may have to touch at the code later.
All in all, static assert kills the overloads if it is selected for instantiation. Given that it is a nullary function and there is only one nullary overload per function name, the only case where the overload is selected is when the default constructor is called. In the end, that doesn't change much to kill it from the overloading set or to kill it if it is instantiated.
Side note: clang implementation use the same construct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
others may have to touch at the code later.
Quite :)
platform/Span.h
Outdated
*/ | ||
reference operator[](index_type index) const | ||
{ | ||
return _data[index]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be a good place for an MBED_ASSERT
range-check on index
? Though maybe that should be limited to debug builds, not normal develop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, that is the place where it potentially costs the most. MBED_ASSERT are preserved in develop builds.
Here is some data about the cost of checks: https://godbolt.org/z/E5c1b6
MINIMAL_CHECK tests the upper boundary while FULL_CHECK tests the lower and upper boundary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think it's too much for develop builds. Could do it conditionalised on MBED_DEBUG
or a custom flag. Anyway, can be added later.
* @param count Number of elements viewed. | ||
* | ||
* @pre [ptr, ptr + count) must be be a valid range. | ||
* @pre count must be equal to extent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extent
platform/Span.h
Outdated
_data(elements) { } | ||
|
||
/** | ||
* Return the size of the array viewed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quite a few references to "array" or "array viewed" in comments where it should now be span.
@kjbracey-arm I evaluated a bit more the static assert option and it should be good as long as we have a single conversion constructor. SFINAE would have to be reintroduced if we add conversion functions from other type of containers. af69e1f commit message contains a more detailed explanation. |
@pan- I can't locate tests and documentation PR for this addition |
@0xc0170 The number of tests we can set are rather limited and trivial as the type is just a constrained pointer (and optionally a size) to a contiguous sequence. Precondition violation will either generate a failure are runtime or compile time and we cannot test these unfortunately. I can add some test but they will have to be converted to the new unit test framework later. I will open an entry in the handbook; the class documentation is a good start. |
Copy edit file, mostly for consistent capitalization, punctuation and tense.
/** | ||
* Nonowning view to a sequence of contiguous elements. | ||
* | ||
* Spans encapsulate a pointer to a sequence of contiguous elements and its size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Query: What does "it" refer to in this sentence?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it refers to the sequence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that works - I get it from context, but don't think you can use "it" to reach into the "to" of a clause. It reads as either "encapsulates ((a pointer to a sequence of contiguous elements) and (the pointer's size))" or "encapsulates a pointer to ((a sequence of contiguous elements) and (the sequence's size))".
How about "Spans encapsulate the address and size of a sequence of contiguous elements"? That also avoids the indefiniteness of "a pointer" versus "its length".
Although I still like using the word pointer. How about "Spans encapsulate a pointer and length providing a view over a sequence of contiguous elements".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some copy edits. Please review them to make sure I didn't accidentally change the meaning of anything.
platform/Span.h
Outdated
* at runtime. | ||
*/ | ||
#define SPAN_DYNAMIC_EXTENT -1 | ||
|
||
/** | ||
* Non owning view to a sequence of contiguous elements. | ||
* Nonowning view to a sequence of contiguous elements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be Non-owning ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use hyphens when the last letter of the suffix and the first letter of the prefix are the same [proto-oncology, non-negotiable, post-traumatic and so on]. There are some exceptions, such as when the suffix is a proper noun or an acronym. Then, use a hyphen regardless of the first letter of the suffix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Thanks Amanda.
Looks like a lot of the CS literature spell it wrong.
platform/Span.h
Outdated
* | ||
* @par Operations | ||
* | ||
* Span objects can be copied and assigned like regular value types with the help | ||
* of copy constructor and copy assignment (=) operator. | ||
* of copy constructor and copy assignment (=) operators. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is singular in this context; I can rephrase it: "with the help of the copy constructor or the copy assignment (=) operator "
platform/Span.h
Outdated
* | ||
* Span can be sliced from the beginning of the sequence (first()), from the end | ||
* You can splice Span from the beginning of the sequence (first()), from the end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slice is more commonly used in CS in that context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a typo on my part. My apologies.
platform/Span.h
Outdated
* | ||
* @note OtherElementType(*)[] should be convertible to ElementType(*)[]. | ||
* @note OtherElementType(*)[] is convertible to ElementType(*)[]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the enunciation of a requirement; not the enunciation of the fact. What form should be used ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. What do you think of "must be"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is more precise 👍 .
@AnotherButler I've updated the documentation according to our discussion above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@deepikabhavnani @c1728p9 @SenRamakri @kegilbert Could one of y'all take a look at this and OK it before CI? |
Actually, gonna start CI with the hope that it's gets a final OK, since this this blocks #7822 /morph build |
Build : SUCCESSBuild number : 2901 Triggering tests/morph test |
Exporter Build : SUCCESSBuild number : 2528 |
Test : SUCCESSBuild number : 2653 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. Thanks for bringing this main-line @pan-
Description
The Span class allows the creation of views over contiguous memory. The view
do not own memory, is typed and has a length. It can be used as a replacement of
the traditional pair of pointer and size in parameters or class fields.
Main operations:
not.
The Span class came in two flavors:
a single pointer,
and the size of the view.
Pull request type