1.. Copyright (c) 2016, Johan Mabille, Sylvain Corlay and Wolf Vollprecht 2 3 Distributed under the terms of the BSD 3-Clause License. 4 5 The full license is in the file LICENSE, distributed with this software. 6 7.. _xtensor-assign-label: 8 9Assignment 10========== 11 12In this section, we consider the class ``xarray`` and its semantic bases (``xcontainer_semantic`` and 13``xsemantic_base``) to illustrate how the assignment works. `xtensor` provides different mechanics of 14assignment depending on the type of expression. 15 16Extended copy semantic 17~~~~~~~~~~~~~~~~~~~~~~ 18 19``xarray`` provides an extended copy constructor and an extended assignment operator: 20 21.. code:: 22 23 template <class E> 24 xarray(const xexpression<E>&); 25 26 template <class E> 27 self_type& operator=(const xexpression<E>& e); 28 29The assignment operator forwards to ``xsemantic_base::operator=`` whose implementation is given below: 30 31.. code:: 32 33 template <class E> 34 derived_type& operator=(const xexpression<E>& e) 35 { 36 temporary_type tmp(e); 37 return this->derived_cast().assign_temporary(std::move(tmp)); 38 } 39 40Here ``temporary_type`` is ``xarray``, the assignment operator computes the result of the expression in 41a temporary variable and then assigns it to the ``xarray`` instance. This temporary variable avoids aliasing 42when the array is involved in the rhs expression where broadcasting happens: 43 44.. code:: 45 46 xarray<double> a = {1, 2, 3, 4}; 47 xarray<double> b = {{1, 2, 3, 4}, 48 {5, 6, 7, 8}}; 49 a = a + b; 50 51The extended copy constructor calls ``xsemantic_base::assign`` which calls ``xcontainer::assign_xexpression``. 52This two-steps invocation allows to provide an uniform API (assign, plus_assign, minus_assign, etc) in the 53top base class while specializing the implementations in inheriting classes (``xcontainer_semantic`` and 54``xview_semantic``). ``xcontainer::assign_xexpression`` eventually calls the free function ``xt::assign_xexpression`` 55which will be discussed in details later. 56 57The behavior of the extended copy semantic can be summarized with the following diagram: 58 59.. image:: extended_copy_semantic.svg 60 61Computed assignment 62~~~~~~~~~~~~~~~~~~~ 63 64Computed assignment can be achieved either with traditional operators (``operator+=``, ``operator-=``) or 65with the corresponding assign functions (``plus_assign``, ``minus_assign``, etc). The computed assignment 66operators forwards to the extended assignment operator as illustrated below: 67 68.. code:: 69 70 template <class D> 71 template <class E> 72 inline auto xsemantic_base<D>::operator+=(const xexpression<E>& e) -> derived_type& 73 { 74 return operator=(this->derived_cast() + e.derived_cast()); 75 } 76 77The computed assign functions, like ``assign`` itself, avoid the instantiation of a temporary variable. 78They call the overload of ``computed_assign`` which, in the case of ``xcontainer_semantic``, simply forwards 79to the free function ``xt::computed_assign``: 80 81.. code:: 82 83 template <class D> 84 template <class E> 85 inline auto xsemantic_base<D>::plus_assign(const xexpression<E>& e) -> derived_type& 86 { 87 return this->derived_cast().computed_assign(this->derived_cast() + e.derived_cast()); 88 } 89 90 template <class D> 91 template <class E> 92 inline auto xcontainer_semantic<D>::computed_assign(const xexpression<E>& e) -> derived_type& 93 { 94 xt::computed_assign(*this, e); 95 return this->derived_cast(); 96 } 97 98Again this two-steps invocation allows to provide a uniform API in ``xsemantic_base`` and specializations 99in the inheriting semantic classes. Besides this allows some code factorization since the assignment 100logic is implemented only once in ``xt::computed_assign``. 101 102Scalar computed assignment 103~~~~~~~~~~~~~~~~~~~~~~~~~~ 104 105Computed assignment operators involving a scalar are similar to computed assign methods: 106 107.. code:: 108 109 template <class D> 110 template <class E> 111 inline auto xsemantic_base<D>::operator+=(const E& e) -> disable_xexpression<E, derived_type&> 112 { 113 return this->derived_cast().scalar_computed_assign(e, std::plus<>()); 114 } 115 116 template <class D> 117 template <class E, class F> 118 inline auto xcontainer_semantic<D>::scalar_computed_assign(const E& e, F&& f) -> derived_type& 119 { 120 xt::scalar_computed_assign(*this, e, std::forward<F>(f)); 121 return this->derived_cast(); 122 } 123 124The free function ``xt::scalar_computed_assign`` contains optimizations specific to scalars. 125 126Expression assigners 127~~~~~~~~~~~~~~~~~~~~ 128 129The three main functions for assigning expressions (``assign_xexpression``, ``computed_assign`` and 130``scalar_computed_assign``) have a similar implementation: they forward the call to the 131``xexpression_assigner``, a template class that can be specialized according to the expression 132tag: 133 134.. code:: 135 136 template <class E1, class E2> 137 inline void assign_xexpression(xexpression<E1>& e1, const xexpression<E2>& e2) 138 { 139 using tag = xexpression_tag_t<E1, E2>; 140 xexpression_assigner<tag>::assign_xexpression(e1, e2); 141 } 142 143 template <class Tag> 144 class xexpression_assigner : public xexpression_assigner_base<Tag> 145 { 146 public: 147 148 using base_type = xexpression_assigner_base<Tag>; 149 150 template <class E1, class E2> 151 static void assign_xexpression(xexpression<E1>& e1, const xexpression<E2>& e2); 152 153 template <class E1, class E2> 154 static void computed_assign(xexpression<E1>& e1, const xexpression<E2>& e2); 155 156 template <class E1, class E2, class F> 157 static void scalar_computed_assign(xexpression<E1>& e1, const E2& e2, F&& f); 158 159 // ... 160 }; 161 162`xtensor` provides specializations for ``xtensor_expression_tag`` and ``xoptional_expression_tag``. 163When implementing a new function type whose API is unrelated to the one of ``xfunction_base``, 164the ``xexpression_assigner`` should be specialized so that the assignment relies on this specific API. 165 166assign_xexpression 167~~~~~~~~~~~~~~~~~~ 168 169The ``assign_xexpression`` methods first resizes the lhs expression, it chooses an assignment 170method depending on many properties of both lhs and rhs expressions. One of these properties, computed 171during the resize phase, is the nature of the assignment: trivial or not. The assignment is said to be 172trivial when the memory layout of the lhs and rhs are such that assignment can be done by iterating over 173a 1-D sequence on both sides. In that case, two options are possible: 174 175- if ``xtensor`` is compiled with the optional ``xsimd`` dependency, and if the layout and the 176 ``value_type`` of each expression allows it, the assignment is a vectorized index-based loop 177 operating on the expression buffers. 178- if the ``xsimd`` assignment is not possible (for any reason), an iterator-based loop operating 179 on the expresion buffers is used instead. 180 181These methods are implemented in specializations of the ``trivial_assigner`` class. 182 183When the assignment is not trivial, :ref:`stepper-label` are used to perform the assignment. Instead of 184using ``xiterator`` of each expression, an instance of ``data_assigner`` holds both steppers and makes 185them step together. 186 187.. image:: assign_xexpression.svg 188 189computed_assign 190~~~~~~~~~~~~~~~ 191 192The ``computed_assign`` method is slightly different from the ``assign_xexpression`` method. After 193resizing the lhs member, it checks if some broadcasting is involved. If so, the rhs expression is 194evaluated into a temporary and the temporary is assigned to the lhs expression, otherwise rhs is 195directly evaluated in lhs. This is because a computed assignment always implies aliasing (meaning 196that the lhs is also involved in the rhs): ``a += b;`` is equivalent to ``a = a + b;``. 197 198.. image:: computed_assign.svg 199 200scalar_computed_assign 201~~~~~~~~~~~~~~~~~~~~~~ 202 203The ``scalar_computed_assign`` method simply iterates over the expression and applies the scalar 204operation on each value: 205 206.. code:: 207 208 template <class Tag> 209 template <class E1, class E2, class F> 210 inline void xexpression_assigner<Tag>::scalar_computed_assign(xexpression<E1>& e1, const E2& e2, F&& f) 211 { 212 E1& d = e1.derived_cast(); 213 std::transform(d.cbegin(), d.cend(), d.begin(), 214 [e2, &f](const auto& v) { return f(v, e2); }); 215 } 216 217