executors / futures

A proposal for a futures programming model for ISO C++
22 stars 7 forks source link

Don't require the `promise_contract` return type to be a `std::pair` #120

Open dhollman opened 6 years ago

dhollman commented 6 years ago

I think we should specify the promise_contract_t return type in terms of members (currently first and second, but I think we can do better than that) rather than strictly constraining it to be a specialization of std::pair (maybe we haven't said this explicitly yet, but it does feel like this is implied). In the case of the polymorphic return type, this means we'd have to specify a relatively trivial concrete type, but I think that's fine.

One of the main reasons to do this is that I can see us wanting to add traits or other functionality to the return type that don't really belong on the promise part or on the future_token part, and if we have a pair as the means of binding these things together, we're kind of stuck. It's pretty similar to the logic behind providing a passthrough on_value() function in this respect.

griwes commented 6 years ago

I'd go even further and require only that the type can be destructured using structured bindings - I understand how this can be controversial, though.

RedBeard0531 commented 6 years ago

Unless we actually need this for something, this sounds like a good thing to just leave up to L(E)WG since they will probably have opinions. std::pair is the simplest thing we can say, so my default opinion is to just leave it as that for now.

griwes commented 6 years ago

I think the important take-away from what David says is we may not know what we need at this point, which is why we shouldn't make the concept actually require a std::pair (and instead, a concept that is fulfilled by std::pair.) This is strictly additive to the paper, but much less limiting on executor authors.

dhollman commented 6 years ago

@RedBeard0531 I do already have some specific reasons for this. For instance, in my implementation sketches, I've found it useful to attach a invoke_then_execute() function for cases when the concrete type of both the "destination" executor and the future type are known at construction time, but not the closure to pass to then execute (and the object requesting the promise contract needs to use polymorphic storage). There may also be some value in type-erasing the "source" executor at that same point, or even the closure itself (if available), to interoperate with non-ThenExecutors. I haven't fully convinced myself this is necessary (and thus it isn't in V1 of the paper), but I don't want to be locked out of it by our design.

jaredhoberock commented 6 years ago

I'd also appreciate the flexibility of not having to return a particular type from this property. My own executors would probably choose to return a tuple-like type spelled differently than std::pair.

dhollman commented 6 years ago

@jaredhoberock I'm glad you brought it up and not me. I was going to mention the case of std::pair being unavailable on the hardware in question, but I'm often accused of resorting to this concern too often.

jaredhoberock commented 6 years ago

@dhollman Yes, there's the issue that some Standard Library components will not be available to all executors. However, another issue worth considering is that some authors might prefer to define a promise contract type which is more elaborate than a std::pair and which may contain additional interfaces beyond .first and .second.

dhollman commented 6 years ago

@jaredhoberock exactly my point :-)

RedBeard0531 commented 6 years ago

@griwes Sure, but it isn't like this paper is going to be merged in tomorrow. This is still proposed wording that hasn't been chewed on by L(E)WG and will likely see some modifications by them anyway. Given that my default position is KISS. I'd focus on simplicity for ease of reading and understanding both by us and the rest of the committee. Every unnecessary sentence (and decay_t...) cuts against that. This paper is already somewhat long to read all at once.

@dhollman fair enough. I'm curious what kind of hardware can't support std::pair<T,U> given that it is defined as struct pair { T first; U second; };, but I'll take your word for it.

Just to be clear, I'm not opposed to any complexity, I just want to ensure it is justified. This sounds like it may be justified.

dhollman commented 6 years ago

@RedBeard0531 std::pair is way more complex than you're giving it credit for. It has been said that std::pair is "right on the edge of what we can express with C++" (can't find the source of the quote now, but I've heard several people say it). And yes, such an architecture does exist. At least at times in its history (now included?), CUDA hasn't supported the standard library, for instance.

RedBeard0531 commented 6 years ago

@dhollman are you sure that was said about pair rather than tuple. This is the whole definition of pair. It is required to be implemented as a simple struct with two elements. This is an observable fact due to StandardLayout. tuple is really complicated though...

dhollman commented 6 years ago

@RedBeard0531 I'm pretty sure it was pair. The complexity arises from the constructors. There's a lot of nastiness hidden by that EXPLICIT specification macro IIRC. Anyway, this is a bit beside the point. My main point was Jared's point that you might want to add some members for use outside of generic context (and that we might want to add some members to the generic concept at some point)