Skip to content

closure_type_t in xfunction #1080

@JohanMabille

Description

@JohanMabille

As stated in #1070, using const_closure_type to store operands in an xfunction may prevent a call of the move constructor and lead to a call of the copy constructor (resulting in performance issue in the best case, and compilation issues in the worst case).

Indeed the const_closure_type_t of an rvalue of type T is const T, thus an rvalue operand of a function will be stored as a const value, which is not movable by default. A few solutions can fix this problem:

  1. Use closure_type_t instead of const_closure_type_t

In that case, the closure_type of an rvalue of type T is T, which is movable by default. The problem is that references to non-const object will not be stored as const references in the xfunction object:

xt::xarray<double> a, b;
auto f = a + b;

Here a and b will be stored as xt::array<double>&, allowing the xfunction object to potentially modify them, which is semantically wrong. If this code had to be written without universal references, it would use const references for the type of operand parameters, not mutable references (and that's the way pre-c++11 expression systems are written). It might not be a big deal for now since all the methods of xfunction are const, but this might change (and when that happens, I'm not convinced we'll still have this problem in mind). Besides this breaks the const-correctness from my point of view (the node of an expression tree is not supposed to change its operands).

  1. Explicitly write the move constructor / assign operator

Another solution is to explicitly write the move constructor / assign operator to const cast the operands stored as const T and then call their move constructor. This, again, breaks the const-correctness. Indeed, when an object is moved, its final state is not guaranteed to be valid, which is inconsistent with a const object.
However, since an operand stored as a value (const or mutable) is owned by the xfunction, it does not really matter, a change on this operand has no side effect (contrary to the change on a reference on an expression that might be used elsewhere); besides, storing value operands as const T instead of T ensures that we always call the const overload of the operand's methods. As for previous solution, it is not a big deal for now since the methods of xfunction are all const (ensuring the call of the const overloads of the operand's methods), but this might change.

  1. Use another type of closure type

If we don't have to ensure the call of const methods on the operands of a function, another (simpler) solution is to store lvalue operands as const references and rvalue operands as mutable values. This ensures the xfunction and its operands can always be moved, and that no side effect arises. This solution requires a new kind of closure type (always const for reference type, never const for value type).

Indeed we don't want to change the implementation of const_closure_type because this is a more general concept than its usage in xtensor, and one could need the guarantee to call the const overloads as explained in 2).

I think the best solution for xtensor is the last one:

  • we don't need to ensure a call of const overloads on operands
  • we want to avoid potential side effects of xfunction on its operands (no change allowed on lvalue operands, a potential change on an rvalue operand (i.e. owned by the xfunction) doesn't matter).

NB: Note that this issue is not restricted to xfunction, a lot of expressions in xtensor indirectly use const_closure_type through const_xclosure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions