// Provide the C++ wrapper for {{operator_name}} of {{lhs}} and {{rhs}}
{{out}} operator{{operator}}(const {{lhs.passByReference}}, const {{rhs.passByReference}}) {
  {% if lhs != "BoutReal" and rhs != "BoutReal" %}
    ASSERT1_FIELDS_COMPATIBLE(lhs, rhs);
  {% endif %}

  {{out.field_type}} {{out.name}}{emptyFrom({{lhs.name if lhs.field_type == out.field_type else rhs.name}})};
  checkData({{lhs.name}});
  checkData({{rhs.name}});

  {% if out == "Field3D" %}
    {% if lhs == rhs == "Field3D" %}
      {{out.name}}.setRegion({{lhs.name}}.getMesh()->getCommonRegion({{lhs.name}}.getRegionID(),
                                                                     {{rhs.name}}.getRegionID()));
    {% elif lhs == "Field3D" %}
      {{out.name}}.setRegion({{lhs.name}}.getRegionID());
    {% elif rhs == "Field3D" %}
      {{out.name}}.setRegion({{rhs.name}}.getRegionID());
    {% endif %}
  {% endif %}

  {% if (out == "Field3D") and ((lhs == "Field2D") or (rhs =="Field2D")) %}
    Mesh *localmesh = {{lhs.name if lhs.field_type != "BoutReal" else rhs.name}}.getMesh();

    {% if (lhs == "Field2D") %}
    {{region_loop}}({{index_var}}, {{lhs.name}}.getRegion({{region_name}})) {
    {% else %}
    {{region_loop}}({{index_var}}, {{rhs.name}}.getRegion({{region_name}})) {
    {% endif %}
	const auto {{mixed_base_ind}} = localmesh->ind2Dto3D({{index_var}});
	{% if (operator == "/") and (rhs == "Field2D") %}
           const auto tmp = 1.0 / {{rhs.mixed_index}};
	   for (int {{jz_var}} = 0; {{jz_var}} < localmesh->LocalNz; ++{{jz_var}}){
         	   {{out.mixed_index}} = {{lhs.mixed_index}} * tmp;
        {% else %}
	   for (int {{jz_var}} = 0; {{jz_var}} < localmesh->LocalNz; ++{{jz_var}}){
	           {{out.mixed_index}} = {{lhs.mixed_index}} {{operator}} {{rhs.mixed_index}};
        {% endif %}
	}
	}
  {% elif out == "FieldPerp" and (lhs == "Field2D" or lhs == "Field3D" or rhs == "Field2D" or rhs == "Field3D")%}
    Mesh *localmesh = {{lhs.name if lhs.field_type != "BoutReal" else rhs.name}}.getMesh();

    {{region_loop}}({{index_var}}, {{out.name}}.getRegion({{region_name}})) {
            int yind = {{lhs.name if lhs == "FieldPerp" else rhs.name}}.getIndex();
            const auto {{mixed_base_ind}} = localmesh->indPerpto3D({{index_var}}, yind);
            {% if lhs != "FieldPerp" %}
	    {{out.index}} = {{lhs.base_index}} {{operator}} {{rhs.index}};
            {% else %}
	    {{out.index}} = {{lhs.index}} {{operator}} {{rhs.base_index}};
            {% endif %}
	}
  {% elif (operator == "/") and (rhs == "BoutReal") %}
      const auto tmp = 1.0 / {{rhs.index}};
      {{region_loop}}({{index_var}}, {{out.name}}.getValidRegionWithDefault({{region_name}})) {
         {{out.index}} = {{lhs.index}} * tmp;
      }
  {% else %}
    {{region_loop}}({{index_var}}, {{out.name}}.getValidRegionWithDefault({{region_name}})) {
	    {{out.index}} = {{lhs.index}} {{operator}} {{rhs.index}};
	}
  {% endif %}

  checkData({{out.name}});
  return {{out.name}};
}

{% if out.field_type == lhs.field_type %}
// Provide the C++ operator to update {{lhs}} by {{operator_name}} with {{rhs}}
{{lhs}} &{{lhs}}::operator{{operator}}=(const {{rhs.passByReference}}) {
  // only if data is unique we update the field
  // otherwise just call the non-inplace version
  if (data.unique()) {
    {% if lhs != "BoutReal" and rhs != "BoutReal" %}
      ASSERT1_FIELDS_COMPATIBLE(*this, rhs);
    {% endif %}

    {% if (lhs == "Field3D") %}
      // Delete existing parallel slices. We don't copy parallel slices, so any
      // that currently exist will be incorrect.
      clearParallelSlices();

    {% endif %}
    checkData(*this);
    checkData({{rhs.name}});

    {% if lhs == rhs == "Field3D" %}
      regionID = fieldmesh->getCommonRegion(regionID, {{rhs.name}}.regionID);
    {% endif %}


  {% if (lhs == "Field3D") and (rhs =="Field2D") %}
    {{region_loop}}({{index_var}}, {{rhs.name}}.getRegion({{region_name}})) {
	const auto {{mixed_base_ind}} = fieldmesh->ind2Dto3D({{index_var}});
	{% if (operator == "/") and (rhs == "Field2D") %}
           const auto tmp = 1.0 / {{rhs.mixed_index}};
	   for (int {{jz_var}} = 0; {{jz_var}} < fieldmesh->LocalNz; ++{{jz_var}}){
		   (*this)[{{mixed_base_ind}} + {{jz_var}}] *= tmp;
        {% else %}
           for (int {{jz_var}} = 0; {{jz_var}} < fieldmesh->LocalNz; ++{{jz_var}}){
	           (*this)[{{mixed_base_ind}} + {{jz_var}}] {{operator}}= {{rhs.index}};
        {% endif %}
	}
	}
  {% elif lhs == "FieldPerp" and (rhs == "Field3D" or rhs == "Field2D")%}
    Mesh *localmesh = this->getMesh();

    {{region_loop}}({{index_var}}, this->getRegion({{region_name}})) {
            int yind = this->getIndex();
            const auto {{mixed_base_ind}} = localmesh->indPerpto3D({{index_var}}, yind);
            (*this)[{{index_var}}] {{operator}}= {{rhs.base_index}};
	}
  {% elif rhs == "FieldPerp" and (lhs == "Field3D" or lhs == "Field2D")%}
    Mesh *localmesh = this->getMesh();

    {{region_loop}}({{index_var}}, {{rhs.name}}.getRegion({{region_name}})) {
            int yind = {{rhs.name}}.getIndex();
            const auto {{mixed_base_ind}} = localmesh->indPerpto3D({{index_var}}, yind);
            (*this)[{{base_ind_var}}] {{operator}}= {{rhs.index}};
	}
  {% elif (operator == "/") and (lhs == "Field3D" or lhs == "Field2D") and (rhs =="BoutReal") %}
    const auto tmp = 1.0 / {{rhs.index}};
    {{region_loop}}({{index_var}}, this->getRegion({{region_name}})) {
        (*this)[{{index_var}}] *= tmp;
    }
  {% else %}
    {{region_loop}}({{index_var}}, this->getRegion({{region_name}})) {
      (*this)[{{index_var}}] {{operator}}= {{rhs.index}};
    }
  {% endif %}

  {% if lhs == "Field3D" %}
    track(rhs, "operator{{operator}}=");
  {% endif %}

    checkData(*this);

  } else {
  {% if lhs == "Field3D" %}
    track(rhs, "operator{{operator}}=");
  {% endif %}
    (*this) = (*this) {{operator}} {{rhs.name}};
  }
  return *this;
}
{% endif %}
