-
Notifications
You must be signed in to change notification settings - Fork 26
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
Introduce beginnings of prototype/reference implementation, adjust wording #55
Conversation
wording based upon prototyping experience.
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.
Hi Carter, I put in some comments based on our review of the pull request. There are number of points we probably need to discuss. Let me know if you think its best to have a telecon.
P0009/P0009.bs
Outdated
@@ -1805,28 +1796,16 @@ namespace std { | |||
namespace experimental { | |||
namespace fundamentals_v3 { | |||
|
|||
template<class T> | |||
struct accessor_basic { |
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 believe this is not a good choice. Besides the fact I find it a bit awkward (but thats my "I like C++ usage as C with classes" inner self ;-) ), this would make it more cumbersome to provide accessors which are partially or fully specialised. The original arguments is whether accessor should be defined in terms of a nested class like the layout. We felt here that it would make the language definition more cumbersome, for not much gain. The template argument being templates is something which happens throughout C++ anyway (for example allocators for containers). Thoughts?
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.
The gain is to be able to define and compose access properties independently of the type to which they are applied; e.g., atomic, streaming, etc. Cumbersome to compose access properties if the ElementType must be carried along during composition, as apposed to being applied after the fact. Consider a "rebind" or "apply to" interface:
auto ax = apply_property( x , access_atomic );
where
inline constexpr const access_atomic_t access_atomic;
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 don't see how it's any more awkward or cumbersome to include an element type. You have:
auto ax = apply_property(x, access_atomic<T>);
where
template <typename T>
inline constexpr access_atomic_t<T> access_atomic;
or even just:
auto ax = access_atomic(x);
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.
Actually, the code you gave originally:
auto ax = apply_property( x , access_atomic );
can just work as is, assuming an implementation of apply_property
that deduces the type of x
and acts on some sentinel access_atomic
, especially if we add an optional rebind
member template, like on allocator. I wouldn't be opposed to that, because that's something I think we could reasonably allow at the concept level without losing the ability to allow accessors to restrict the types they act on and partially specialize.
@@ -1112,7 +1110,7 @@ struct layout_left { | |||
constexpr mapping& operator=(const mapping<OtherExtents>& other); | |||
|
|||
// [mdspan.layout.left.ops], layout_left::mapping operations | |||
Extents get_extents() const noexcept; |
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.
The rename from get_extents to extents is fine, and more in line with what the standard does (there are not many classes with get_.. members). Returning const Extents& is more problematic we believe, since it probably now requires that the mapping stores an extent. Before it could store something else which could be used to create an 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.
at least weaken to convertable to Extents so an implementation is not forced to have the penalty of constructing an extent every time the function is called.
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.
From the CppCoreGuidelines:
What is “cheap to copy” depends on the machine architecture, but two or three words (doubles, pointers, references) are usually best passed by value.
Most extents
will be smaller, so our concrete types should return by value IMHO. But I agree, the concept should not have this constraint.
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.
Doesn't that mean as in the case below that we end up having problems with stuff like auto on the return type of extents? Also convertable to Extents doesn't solve the issue of creating a copy. I either return a reference to something that has to exist in mapping (in which case changes to mapping would be observable in the returned reference I got via auto& e=m.extents();
, or I return a copy in which case I don't need to prescribe what is in mapping.
So two options:
- return const Extents& and mapping has now a real member of Extents not just exposition only, but it fixes the "I need to create a copy" issue.
- leave it as is.
Thoughts on what is preferable?
P0009/P0009.bs
Outdated
<b>26.7.�.2.1 `accessor_basic` operations [mdspan.accessor.basic.ops]</b> | ||
|
||
```c++ | ||
constexpr reference operator()(pointer p, ptrdiff_t i) const noexcept; |
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.
We think it is okish to delete that operator, but there are drawbacks and benefits:
- benefit: common case, pointer is just T* has now one less inlined function.
- drawback: requires pointer to be more often a wrapper object which contains all the stuff the accessor would contain, for example for the atomic accessor, pointer is not anymore T*. It needs to be some wrapper class which is convertible to T* and which has an [] operator.
- drawback: pointer must be templated on reference, since its operator [] must return that.
- drawback: no clear separation of access and storage.
- e.g. if I want to use some intrinsic to access the data in previous solution that intrinsic is in the operator of the accessor, now I write a little class containing a pointer and a [] operator, and the implementation of the [] operator contains the intrinsic
It seems this makes stuff more complex down the road. My main issue is that pointer[int] must return a reference, which means pointer and reference are not independent any more.
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.
Yep - not independent. The pointer and reference should have a strong relationship. The pointer is carried around as a member of mdspan and is handle to storage, the reference is an associated handle to accessing an element of that storage. Should just be able to copy the storage handle and everything works - no extra wrappers.
See the prototype where template<class T> using pointer = T *;
for the accessor_basic.
P0009/P0009.bs
Outdated
<td></td> | ||
<td>*Requires:* `iterator_traits<A::pointer>::reference` shall be convertible to `A::reference` </td> | ||
<td>*Requires:* `A::pointer<T>` shall be `DefaultConstructible` and `CopyAssignable`. *[Note:* This may be `T*` or a proxy that operates like `T*`. *--end note]*</td> |
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 doesn't seem to be enough in terms of what pointer is. This for example doesn't define that operator[] (ptrdiff_t) exists or how that behaves. It doesn't define that operator* () exists or what it does. And we need arithmetic on those to allow subspan. If we define all of those than we are probably getting really close to random access iterator. Is there anything specific in random access iterator we don't want or shouldn't require for pointer?
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.
Operational spec required p[i]
to return a reference to the offset - so that is still there.
An iterator also requires *p
, ++p
, and p++
- which should not be required by operational spec.
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.
The point about *p
, ++p
, and p++
not needing to exist is interesting. Do you envision any cases where this would be necessary? You're losing a lot of the power you get by separating storage from access, and it's not clear how you would form the codomain span with something that doesn't meet the requirements of RandomAccessIterator
.
|
||
</td> | ||
<td>`m.extents()`</td> | ||
<td>convertable to `E`</td> |
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.
Is there a good reason to not return an actual Extent? We'd rather have the more specific one. vector is a good example where this is a problem, since it returns something convertible to bool and not a bool. Which means that for example doing auto and than using the variable in a templated function is an issue.
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.
Convertable allows return by value or return by constant reference, which allows an implementation to avoid the overhead of being required to construct and return a copy.
|
||
```c++ | ||
template<class... IndexType> | ||
static constexpr index_type required_span_size(IndexType... dynamic_extents) noexcept; |
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.
We discussed this and were not in favor of replicating too much stuff in mdspan which exists in mapping. But it wasn't a super strong preference. If you like it to be in here that is fine.
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's not just about replicating too much stuff. This won't work for non-unique, stateful mappings, so the operational semantics would have to somehow exclude those mappings, and the mapping would have to somehow advertise that its statefulness affects its required span size. I'm okay with putting it back in if those problems are solved, but this pull request doesn't solve those problems AFAICT.
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.
The mapping
version requires construction of a mapping object and then function call on that object, a syntactically long two-step operation . I believe this will be a frequent pattern and the shortcut is a desirable convenience. The other single-step operations could be removed.
|
||
<br/> | ||
|
||
```c++ | ||
constexpr index_type size() const noexcept; |
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.
We believe we need size and unique_size. One example is I want to pack a strided view into an MPI buffer. I need to know how large to make that buffer, and its not required_span_size. And if we add size we probably also need a function which returns something discounting all the replicated entries in the mapping.
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.
Pull them from the mapping :-)
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.
The reasoning for having size()
and unique_size
here is for consistency with container-like types (this is going into the containers subclause of the standard after all) in the case of size()
, and for clarity as to which size the size()
method refers to in the case of unique_size
.
Closing this now. With the just merged commit of a reference implementation we should have gotten everything transferred from here. |
Introduce beginnings of prototype / reference implementation based upon updating prior prototypes to new wording.
Adjust wording based upon prototyping experience.
Non-trivial change to the
Accessor
to allow a non-templateAccessor
class to denote access properties independent of the element type.