Let interfaces control movability - #280
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #280 +/- ##
==========================================
+ Coverage 72.87% 72.90% +0.03%
==========================================
Files 9 9
Lines 822 823 +1
Branches 208 208
==========================================
+ Hits 599 600 +1
Misses 24 24
Partials 199 199
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
We could allow move construction unconditionally and just prevent allocator-aware move construction and move assignment. Do we want to do this? |
|
Polymorphic (owning) is unconditionally move constructible, I think we need to do the same for protocol for consistency with other standard library types. |
Are you leaning more towards gating movability requirements based on the allocator than the interface? An alternative approach we could take is allow unconditional move construction and assignment, UNLESS the user-provided allocator explicitly needs to know how to generate the move() function (i.e. POCMA or always_equal is false) |
|
Any U in polymorphic must be copyable. We don't have this constraint in protocol so have more freedom than I'd initially thought. We should enumerate and evaluate our options. |
Happy to discuss further. As I see it, the choices are to allow the interface to control this capability, or to make it dependent on the allocator. |
|
Do we want to flesh these options out in the comments and show code that would work and not work under the various options? GitHub markdown is pretty powerful. |
One approach is to let the interface control movability, like this PR adds: struct Movable {};
protocol<Movable> p(SomeCopyableType{});
auto p2 = std::move(p); // moving allowed
auto p3 = p2; // copying allowed
protocol<Movable> p4(SomeMoveOnlyType{}); // ERROR
struct Immovable {
Immovable(const Immovable&) = delete;
Immovable(Immovable&&) = delete;
};
protocol<Immovable> p4(SomeMoveOnlyType{}); // OK
protocol p5(std::move(p4)); // ERROR (moving)
auto p6 = p5; // ERROR (copying)
p4 = SomeCopyableType{}; // OKWe can also recognize, however, that the only time we need to actually call the move constructor of the object is when we dispatch to the So this alternative approach would mean:
I think we still need the interface to constrain movability in this case, as that is the user opting in to say "I care about allocator-aware move construction and move assignment" and therefore requiring movability from a conforming An example: // pretend there's allocator types PocmaFalseAlwaysEq and PocmaTrueNotAlwaysEq
protocol<Movable> p(SomeMoveOnlyType{}); // No ERROR, because std::allocator is_always_equal
protocol<Movable, PocmaFalseAlwaysEq> p1(std::allocator_arg, /*whatever*/, SomeMoveOnlyType{}); // No ERROR, because always equal
protocol<Movable, PocmaTrueNotAlwaysEq> p2(std::allocator_arg, /*whatever*/, SomeMoveOnlyType{}); // ERROR, because allocator-aware move construction would not work.
protocol<Immovable> p3(SomeMoveOnlyType{}); // OK
protocol p4(std::move(p3)); // OK
p3 = std::move(p4); // OK
protocol<Immovable, PocmaTrueNotAlwaysEq> p5(SomeMoveOnlyType{}); // OK
protocol p6(std::move(p5)); // OK
p5 = std::move(p6); // OK because pocma is true.
protocol p7(std::allocator_arg, /* a new allocator */, std::move(p5)); // ERROR because always_equal is false
// If we had PocmaFalseNotAlwaysEq, both move assignment AND alloc-aware move construction would be disabled.I don't think these are necessarily the only methods, but these are the ones I thought of. |
|
This also has some implications for SBO. If the interface requires movability, the SBO can only be used if the type is movable. If the interface does not, SBO can be used for immovable types. |
Left in draft because this poses some important design questions.
Closes #187 and #193.