Skip to content
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

Closed
wants to merge 18 commits into from

Conversation

hcedwar
Copy link
Collaborator

@hcedwar hcedwar commented Jul 7, 2018

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-template Accessor class to denote access properties independent of the element type.

@hcedwar hcedwar requested review from dsunder and crtrott July 7, 2018 00:45
Copy link
Collaborator

@crtrott crtrott left a 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 {
Copy link
Collaborator

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?

Copy link
Collaborator Author

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;

Copy link
Collaborator

@dhollman dhollman Jul 9, 2018

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);

Copy link
Collaborator

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&lt;OtherExtents>& other);

// [mdspan.layout.left.ops], layout_left::mapping operations
Extents get_extents() const noexcept;
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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;
Copy link
Collaborator

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.

Copy link
Collaborator Author

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>
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator

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>
Copy link
Collaborator

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.

Copy link
Collaborator Author

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;
Copy link
Collaborator

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.

Copy link
Collaborator

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.

Copy link
Collaborator Author

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;
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 :-)

Copy link
Collaborator

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.

@crtrott
Copy link
Collaborator

crtrott commented Aug 26, 2018

Closing this now. With the just merged commit of a reference implementation we should have gotten everything transferred from here.

@crtrott crtrott closed this Aug 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants