// SPDX-License-Identifier: Apache-2.0
//
// Copyright 2008-2016 Conrad Sanderson (http://conradsanderson.id.au)
// Copyright 2008-2016 National ICT Australia (NICTA)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

//! \addtogroup subview
//! @{

template <typename eT>
inline subview<eT>::~subview() {
  arma_extra_debug_sigprint_this(this);
}

template <typename eT>
inline subview<eT>::subview(const Mat<eT>& in_m, const uword in_row1, const uword in_col1,
                            const uword in_n_rows, const uword in_n_cols)
    : m(in_m),
      aux_row1(in_row1),
      aux_col1(in_col1),
      n_rows(in_n_rows),
      n_cols(in_n_cols),
      n_elem(in_n_rows * in_n_cols) {
  arma_extra_debug_sigprint_this(this);
}

template <typename eT>
inline subview<eT>::subview(const subview<eT>& in)
    : m(in.m),
      aux_row1(in.aux_row1),
      aux_col1(in.aux_col1),
      n_rows(in.n_rows),
      n_cols(in.n_cols),
      n_elem(in.n_elem) {
  arma_extra_debug_sigprint(arma_str::format("this = %x   in = %x") % this % &in);
}

template <typename eT>
inline subview<eT>::subview(subview<eT>&& in)
    : m(in.m),
      aux_row1(in.aux_row1),
      aux_col1(in.aux_col1),
      n_rows(in.n_rows),
      n_cols(in.n_cols),
      n_elem(in.n_elem) {
  arma_extra_debug_sigprint(arma_str::format("this = %x   in = %x") % this % &in);

  // for paranoia

  access::rw(in.aux_row1) = 0;
  access::rw(in.aux_col1) = 0;
  access::rw(in.n_rows) = 0;
  access::rw(in.n_cols) = 0;
  access::rw(in.n_elem) = 0;
}

template <typename eT>
template <typename op_type>
inline void subview<eT>::inplace_op(const eT val) {
  arma_extra_debug_sigprint();

  subview<eT>& s = *this;

  const uword s_n_rows = s.n_rows;
  const uword s_n_cols = s.n_cols;

  if (s_n_rows == 1) {
    Mat<eT>& A = const_cast<Mat<eT>&>(s.m);

    const uword A_n_rows = A.n_rows;

    eT* Aptr = &(A.at(s.aux_row1, s.aux_col1));

    uword jj;
    for (jj = 1; jj < s_n_cols; jj += 2) {
      if (is_same_type<op_type, op_internal_plus>::yes) {
        (*Aptr) += val;
        Aptr += A_n_rows;
        (*Aptr) += val;
        Aptr += A_n_rows;
      }
      if (is_same_type<op_type, op_internal_minus>::yes) {
        (*Aptr) -= val;
        Aptr += A_n_rows;
        (*Aptr) -= val;
        Aptr += A_n_rows;
      }
      if (is_same_type<op_type, op_internal_schur>::yes) {
        (*Aptr) *= val;
        Aptr += A_n_rows;
        (*Aptr) *= val;
        Aptr += A_n_rows;
      }
      if (is_same_type<op_type, op_internal_div>::yes) {
        (*Aptr) /= val;
        Aptr += A_n_rows;
        (*Aptr) /= val;
        Aptr += A_n_rows;
      }
    }

    if ((jj - 1) < s_n_cols) {
      if (is_same_type<op_type, op_internal_plus>::yes) {
        (*Aptr) += val;
      }
      if (is_same_type<op_type, op_internal_minus>::yes) {
        (*Aptr) -= val;
      }
      if (is_same_type<op_type, op_internal_schur>::yes) {
        (*Aptr) *= val;
      }
      if (is_same_type<op_type, op_internal_div>::yes) {
        (*Aptr) /= val;
      }
    }
  } else {
    for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
      if (is_same_type<op_type, op_internal_plus>::yes) {
        arrayops::inplace_plus(colptr(ucol), val, s_n_rows);
      }
      if (is_same_type<op_type, op_internal_minus>::yes) {
        arrayops::inplace_minus(colptr(ucol), val, s_n_rows);
      }
      if (is_same_type<op_type, op_internal_schur>::yes) {
        arrayops::inplace_mul(colptr(ucol), val, s_n_rows);
      }
      if (is_same_type<op_type, op_internal_div>::yes) {
        arrayops::inplace_div(colptr(ucol), val, s_n_rows);
      }
    }
  }
}

template <typename eT>
template <typename op_type, typename T1>
inline void subview<eT>::inplace_op(const Base<eT, T1>& in, const char* identifier) {
  arma_extra_debug_sigprint();

  const Proxy<T1> P(in.get_ref());

  subview<eT>& s = *this;

  const uword s_n_rows = s.n_rows;
  const uword s_n_cols = s.n_cols;

  arma_debug_assert_same_size(s, P, identifier);

  const bool use_mp =
      arma_config::openmp && Proxy<T1>::use_mp && mp_gate<eT>::eval(s.n_elem);
  const bool has_overlap = P.has_overlap(s);

  if (has_overlap) {
    arma_extra_debug_print("aliasing or overlap detected");
  }

  if ((is_Mat<typename Proxy<T1>::stored_type>::value) || (use_mp) || (has_overlap)) {
    const unwrap_check<typename Proxy<T1>::stored_type> tmp(P.Q, has_overlap);
    const Mat<eT>& B = tmp.M;

    if (s_n_rows == 1) {
      Mat<eT>& A = const_cast<Mat<eT>&>(m);

      const uword A_n_rows = A.n_rows;

      eT* Aptr = &(A.at(aux_row1, aux_col1));
      const eT* Bptr = B.memptr();

      uword jj;
      for (jj = 1; jj < s_n_cols; jj += 2) {
        const eT tmp1 = (*Bptr);
        Bptr++;
        const eT tmp2 = (*Bptr);
        Bptr++;

        if (is_same_type<op_type, op_internal_equ>::yes) {
          (*Aptr) = tmp1;
          Aptr += A_n_rows;
          (*Aptr) = tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_plus>::yes) {
          (*Aptr) += tmp1;
          Aptr += A_n_rows;
          (*Aptr) += tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_minus>::yes) {
          (*Aptr) -= tmp1;
          Aptr += A_n_rows;
          (*Aptr) -= tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_schur>::yes) {
          (*Aptr) *= tmp1;
          Aptr += A_n_rows;
          (*Aptr) *= tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_div>::yes) {
          (*Aptr) /= tmp1;
          Aptr += A_n_rows;
          (*Aptr) /= tmp2;
          Aptr += A_n_rows;
        }
      }

      if ((jj - 1) < s_n_cols) {
        if (is_same_type<op_type, op_internal_equ>::yes) {
          (*Aptr) = (*Bptr);
        }
        if (is_same_type<op_type, op_internal_plus>::yes) {
          (*Aptr) += (*Bptr);
        }
        if (is_same_type<op_type, op_internal_minus>::yes) {
          (*Aptr) -= (*Bptr);
        }
        if (is_same_type<op_type, op_internal_schur>::yes) {
          (*Aptr) *= (*Bptr);
        }
        if (is_same_type<op_type, op_internal_div>::yes) {
          (*Aptr) /= (*Bptr);
        }
      }
    } else  // not a row vector
    {
      if ((s.aux_row1 == 0) && (s_n_rows == s.m.n_rows)) {
        if (is_same_type<op_type, op_internal_equ>::yes) {
          arrayops::copy(s.colptr(0), B.memptr(), s.n_elem);
        }
        if (is_same_type<op_type, op_internal_plus>::yes) {
          arrayops::inplace_plus(s.colptr(0), B.memptr(), s.n_elem);
        }
        if (is_same_type<op_type, op_internal_minus>::yes) {
          arrayops::inplace_minus(s.colptr(0), B.memptr(), s.n_elem);
        }
        if (is_same_type<op_type, op_internal_schur>::yes) {
          arrayops::inplace_mul(s.colptr(0), B.memptr(), s.n_elem);
        }
        if (is_same_type<op_type, op_internal_div>::yes) {
          arrayops::inplace_div(s.colptr(0), B.memptr(), s.n_elem);
        }
      } else {
        for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
          if (is_same_type<op_type, op_internal_equ>::yes) {
            arrayops::copy(s.colptr(ucol), B.colptr(ucol), s_n_rows);
          }
          if (is_same_type<op_type, op_internal_plus>::yes) {
            arrayops::inplace_plus(s.colptr(ucol), B.colptr(ucol), s_n_rows);
          }
          if (is_same_type<op_type, op_internal_minus>::yes) {
            arrayops::inplace_minus(s.colptr(ucol), B.colptr(ucol), s_n_rows);
          }
          if (is_same_type<op_type, op_internal_schur>::yes) {
            arrayops::inplace_mul(s.colptr(ucol), B.colptr(ucol), s_n_rows);
          }
          if (is_same_type<op_type, op_internal_div>::yes) {
            arrayops::inplace_div(s.colptr(ucol), B.colptr(ucol), s_n_rows);
          }
        }
      }
    }
  } else  // use the Proxy
  {
    if (s_n_rows == 1) {
      Mat<eT>& A = const_cast<Mat<eT>&>(m);

      const uword A_n_rows = A.n_rows;

      eT* Aptr = &(A.at(aux_row1, aux_col1));

      uword jj;
      for (jj = 1; jj < s_n_cols; jj += 2) {
        const uword ii = (jj - 1);

        const eT tmp1 = (Proxy<T1>::use_at) ? P.at(0, ii) : P[ii];
        const eT tmp2 = (Proxy<T1>::use_at) ? P.at(0, jj) : P[jj];

        if (is_same_type<op_type, op_internal_equ>::yes) {
          (*Aptr) = tmp1;
          Aptr += A_n_rows;
          (*Aptr) = tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_plus>::yes) {
          (*Aptr) += tmp1;
          Aptr += A_n_rows;
          (*Aptr) += tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_minus>::yes) {
          (*Aptr) -= tmp1;
          Aptr += A_n_rows;
          (*Aptr) -= tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_schur>::yes) {
          (*Aptr) *= tmp1;
          Aptr += A_n_rows;
          (*Aptr) *= tmp2;
          Aptr += A_n_rows;
        }
        if (is_same_type<op_type, op_internal_div>::yes) {
          (*Aptr) /= tmp1;
          Aptr += A_n_rows;
          (*Aptr) /= tmp2;
          Aptr += A_n_rows;
        }
      }

      const uword ii = (jj - 1);
      if (ii < s_n_cols) {
        if (is_same_type<op_type, op_internal_equ>::yes) {
          (*Aptr) = (Proxy<T1>::use_at) ? P.at(0, ii) : P[ii];
        }
        if (is_same_type<op_type, op_internal_plus>::yes) {
          (*Aptr) += (Proxy<T1>::use_at) ? P.at(0, ii) : P[ii];
        }
        if (is_same_type<op_type, op_internal_minus>::yes) {
          (*Aptr) -= (Proxy<T1>::use_at) ? P.at(0, ii) : P[ii];
        }
        if (is_same_type<op_type, op_internal_schur>::yes) {
          (*Aptr) *= (Proxy<T1>::use_at) ? P.at(0, ii) : P[ii];
        }
        if (is_same_type<op_type, op_internal_div>::yes) {
          (*Aptr) /= (Proxy<T1>::use_at) ? P.at(0, ii) : P[ii];
        }
      }
    } else  // not a row vector
    {
      if (Proxy<T1>::use_at) {
        for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
          eT* s_col_data = s.colptr(ucol);

          uword jj;
          for (jj = 1; jj < s_n_rows; jj += 2) {
            const uword ii = (jj - 1);

            const eT tmp1 = P.at(ii, ucol);
            const eT tmp2 = P.at(jj, ucol);

            if (is_same_type<op_type, op_internal_equ>::yes) {
              (*s_col_data) = tmp1;
              s_col_data++;
              (*s_col_data) = tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_plus>::yes) {
              (*s_col_data) += tmp1;
              s_col_data++;
              (*s_col_data) += tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_minus>::yes) {
              (*s_col_data) -= tmp1;
              s_col_data++;
              (*s_col_data) -= tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_schur>::yes) {
              (*s_col_data) *= tmp1;
              s_col_data++;
              (*s_col_data) *= tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_div>::yes) {
              (*s_col_data) /= tmp1;
              s_col_data++;
              (*s_col_data) /= tmp2;
              s_col_data++;
            }
          }

          const uword ii = (jj - 1);
          if (ii < s_n_rows) {
            if (is_same_type<op_type, op_internal_equ>::yes) {
              (*s_col_data) = P.at(ii, ucol);
            }
            if (is_same_type<op_type, op_internal_plus>::yes) {
              (*s_col_data) += P.at(ii, ucol);
            }
            if (is_same_type<op_type, op_internal_minus>::yes) {
              (*s_col_data) -= P.at(ii, ucol);
            }
            if (is_same_type<op_type, op_internal_schur>::yes) {
              (*s_col_data) *= P.at(ii, ucol);
            }
            if (is_same_type<op_type, op_internal_div>::yes) {
              (*s_col_data) /= P.at(ii, ucol);
            }
          }
        }
      } else {
        typename Proxy<T1>::ea_type Pea = P.get_ea();

        uword count = 0;

        for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
          eT* s_col_data = s.colptr(ucol);

          uword jj;
          for (jj = 1; jj < s_n_rows; jj += 2) {
            const eT tmp1 = Pea[count];
            count++;
            const eT tmp2 = Pea[count];
            count++;

            if (is_same_type<op_type, op_internal_equ>::yes) {
              (*s_col_data) = tmp1;
              s_col_data++;
              (*s_col_data) = tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_plus>::yes) {
              (*s_col_data) += tmp1;
              s_col_data++;
              (*s_col_data) += tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_minus>::yes) {
              (*s_col_data) -= tmp1;
              s_col_data++;
              (*s_col_data) -= tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_schur>::yes) {
              (*s_col_data) *= tmp1;
              s_col_data++;
              (*s_col_data) *= tmp2;
              s_col_data++;
            }
            if (is_same_type<op_type, op_internal_div>::yes) {
              (*s_col_data) /= tmp1;
              s_col_data++;
              (*s_col_data) /= tmp2;
              s_col_data++;
            }
          }

          if ((jj - 1) < s_n_rows) {
            if (is_same_type<op_type, op_internal_equ>::yes) {
              (*s_col_data) = Pea[count];
              count++;
            }
            if (is_same_type<op_type, op_internal_plus>::yes) {
              (*s_col_data) += Pea[count];
              count++;
            }
            if (is_same_type<op_type, op_internal_minus>::yes) {
              (*s_col_data) -= Pea[count];
              count++;
            }
            if (is_same_type<op_type, op_internal_schur>::yes) {
              (*s_col_data) *= Pea[count];
              count++;
            }
            if (is_same_type<op_type, op_internal_div>::yes) {
              (*s_col_data) /= Pea[count];
              count++;
            }
          }
        }
      }
    }
  }
}

template <typename eT>
template <typename op_type>
inline void subview<eT>::inplace_op(const subview<eT>& x, const char* identifier) {
  arma_extra_debug_sigprint();

  if (check_overlap(x)) {
    const Mat<eT> tmp(x);

    if (is_same_type<op_type, op_internal_equ>::yes) {
      (*this).operator=(tmp);
    }
    if (is_same_type<op_type, op_internal_plus>::yes) {
      (*this).operator+=(tmp);
    }
    if (is_same_type<op_type, op_internal_minus>::yes) {
      (*this).operator-=(tmp);
    }
    if (is_same_type<op_type, op_internal_schur>::yes) {
      (*this).operator%=(tmp);
    }
    if (is_same_type<op_type, op_internal_div>::yes) {
      (*this).operator/=(tmp);
    }

    return;
  }

  subview<eT>& s = *this;

  arma_debug_assert_same_size(s, x, identifier);

  const uword s_n_cols = s.n_cols;
  const uword s_n_rows = s.n_rows;

  if (s_n_rows == 1) {
    Mat<eT>& A = const_cast<Mat<eT>&>(s.m);
    const Mat<eT>& B = x.m;

    const uword A_n_rows = A.n_rows;
    const uword B_n_rows = B.n_rows;

    eT* Aptr = &(A.at(s.aux_row1, s.aux_col1));
    const eT* Bptr = &(B.at(x.aux_row1, x.aux_col1));

    uword jj;
    for (jj = 1; jj < s_n_cols; jj += 2) {
      const eT tmp1 = (*Bptr);
      Bptr += B_n_rows;
      const eT tmp2 = (*Bptr);
      Bptr += B_n_rows;

      if (is_same_type<op_type, op_internal_equ>::yes) {
        (*Aptr) = tmp1;
        Aptr += A_n_rows;
        (*Aptr) = tmp2;
        Aptr += A_n_rows;
      }
      if (is_same_type<op_type, op_internal_plus>::yes) {
        (*Aptr) += tmp1;
        Aptr += A_n_rows;
        (*Aptr) += tmp2;
        Aptr += A_n_rows;
      }
      if (is_same_type<op_type, op_internal_minus>::yes) {
        (*Aptr) -= tmp1;
        Aptr += A_n_rows;
        (*Aptr) -= tmp2;
        Aptr += A_n_rows;
      }
      if (is_same_type<op_type, op_internal_schur>::yes) {
        (*Aptr) *= tmp1;
        Aptr += A_n_rows;
        (*Aptr) *= tmp2;
        Aptr += A_n_rows;
      }
      if (is_same_type<op_type, op_internal_div>::yes) {
        (*Aptr) /= tmp1;
        Aptr += A_n_rows;
        (*Aptr) /= tmp2;
        Aptr += A_n_rows;
      }
    }

    if ((jj - 1) < s_n_cols) {
      if (is_same_type<op_type, op_internal_equ>::yes) {
        (*Aptr) = (*Bptr);
      }
      if (is_same_type<op_type, op_internal_plus>::yes) {
        (*Aptr) += (*Bptr);
      }
      if (is_same_type<op_type, op_internal_minus>::yes) {
        (*Aptr) -= (*Bptr);
      }
      if (is_same_type<op_type, op_internal_schur>::yes) {
        (*Aptr) *= (*Bptr);
      }
      if (is_same_type<op_type, op_internal_div>::yes) {
        (*Aptr) /= (*Bptr);
      }
    }
  } else {
    for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
      if (is_same_type<op_type, op_internal_equ>::yes) {
        arrayops::copy(s.colptr(ucol), x.colptr(ucol), s_n_rows);
      }
      if (is_same_type<op_type, op_internal_plus>::yes) {
        arrayops::inplace_plus(s.colptr(ucol), x.colptr(ucol), s_n_rows);
      }
      if (is_same_type<op_type, op_internal_minus>::yes) {
        arrayops::inplace_minus(s.colptr(ucol), x.colptr(ucol), s_n_rows);
      }
      if (is_same_type<op_type, op_internal_schur>::yes) {
        arrayops::inplace_mul(s.colptr(ucol), x.colptr(ucol), s_n_rows);
      }
      if (is_same_type<op_type, op_internal_div>::yes) {
        arrayops::inplace_div(s.colptr(ucol), x.colptr(ucol), s_n_rows);
      }
    }
  }
}

template <typename eT>
inline void subview<eT>::operator=(const eT val) {
  arma_extra_debug_sigprint();

  if (n_elem != 1) {
    arma_debug_assert_same_size(n_rows, n_cols, 1, 1, "copy into submatrix");
  }

  Mat<eT>& X = const_cast<Mat<eT>&>(m);

  X.at(aux_row1, aux_col1) = val;
}

template <typename eT>
inline void subview<eT>::operator+=(const eT val) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_plus>(val);
}

template <typename eT>
inline void subview<eT>::operator-=(const eT val) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_minus>(val);
}

template <typename eT>
inline void subview<eT>::operator*=(const eT val) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_schur>(val);
}

template <typename eT>
inline void subview<eT>::operator/=(const eT val) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_div>(val);
}

template <typename eT>
inline void subview<eT>::operator=(const subview<eT>& x) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_equ>(x, "copy into submatrix");
}

template <typename eT>
inline void subview<eT>::operator+=(const subview<eT>& x) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_plus>(x, "addition");
}

template <typename eT>
inline void subview<eT>::operator-=(const subview<eT>& x) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_minus>(x, "subtraction");
}

template <typename eT>
inline void subview<eT>::operator%=(const subview& x) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_schur>(x, "element-wise multiplication");
}

template <typename eT>
inline void subview<eT>::operator/=(const subview& x) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_div>(x, "element-wise division");
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator=(const Base<eT, T1>& in) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_equ>(in, "copy into submatrix");
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator+=(const Base<eT, T1>& in) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_plus>(in, "addition");
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator-=(const Base<eT, T1>& in) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_minus>(in, "subtraction");
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator%=(const Base<eT, T1>& in) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_schur>(in, "element-wise multiplication");
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator/=(const Base<eT, T1>& in) {
  arma_extra_debug_sigprint();

  inplace_op<op_internal_div>(in, "element-wise division");
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator=(const SpBase<eT, T1>& x) {
  arma_extra_debug_sigprint();

  const SpProxy<T1> p(x.get_ref());

  arma_debug_assert_same_size(n_rows, n_cols, p.get_n_rows(), p.get_n_cols(),
                              "copy into submatrix");

  // Clear the subview.
  zeros();

  // Iterate through the sparse subview and set the nonzero values appropriately.
  typename SpProxy<T1>::const_iterator_type cit = p.begin();
  typename SpProxy<T1>::const_iterator_type cit_end = p.end();

  while (cit != cit_end) {
    at(cit.row(), cit.col()) = *cit;
    ++cit;
  }
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator+=(const SpBase<eT, T1>& x) {
  arma_extra_debug_sigprint();

  const SpProxy<T1> p(x.get_ref());

  arma_debug_assert_same_size(n_rows, n_cols, p.get_n_rows(), p.get_n_cols(), "addition");

  // Iterate through the sparse subview and add its values.
  typename SpProxy<T1>::const_iterator_type cit = p.begin();
  typename SpProxy<T1>::const_iterator_type cit_end = p.end();

  while (cit != cit_end) {
    at(cit.row(), cit.col()) += *cit;
    ++cit;
  }
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator-=(const SpBase<eT, T1>& x) {
  arma_extra_debug_sigprint();

  const SpProxy<T1> p(x.get_ref());

  arma_debug_assert_same_size(n_rows, n_cols, p.get_n_rows(), p.get_n_cols(),
                              "subtraction");

  // Iterate through the sparse subview and subtract its values.
  typename SpProxy<T1>::const_iterator_type cit = p.begin();
  typename SpProxy<T1>::const_iterator_type cit_end = p.end();

  while (cit != cit_end) {
    at(cit.row(), cit.col()) -= *cit;
    ++cit;
  }
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator%=(const SpBase<eT, T1>& x) {
  arma_extra_debug_sigprint();

  const uword s_n_rows = (*this).n_rows;
  const uword s_n_cols = (*this).n_cols;

  const SpProxy<T1> p(x.get_ref());

  arma_debug_assert_same_size(s_n_rows, s_n_cols, p.get_n_rows(), p.get_n_cols(),
                              "element-wise multiplication");

  if (n_elem == 0) {
    return;
  }

  if (p.get_n_nonzero() == 0) {
    (*this).zeros();
    return;
  }

  // Iterate over nonzero values.
  // Any zero values in the sparse expression will result in a zero in our subview.
  typename SpProxy<T1>::const_iterator_type cit = p.begin();
  typename SpProxy<T1>::const_iterator_type cit_end = p.end();

  uword r = 0;
  uword c = 0;

  while (cit != cit_end) {
    const uword cit_row = cit.row();
    const uword cit_col = cit.col();

    while (((r == cit_row) && (c == cit_col)) == false) {
      at(r, c) = eT(0);

      r++;
      if (r >= s_n_rows) {
        r = 0;
        c++;
      }
    }

    at(r, c) *= (*cit);

    ++cit;
    r++;
    if (r >= s_n_rows) {
      r = 0;
      c++;
    }
  }
}

template <typename eT>
template <typename T1>
inline void subview<eT>::operator/=(const SpBase<eT, T1>& x) {
  arma_extra_debug_sigprint();

  const SpProxy<T1> p(x.get_ref());

  arma_debug_assert_same_size(n_rows, n_cols, p.get_n_rows(), p.get_n_cols(),
                              "element-wise division");

  // This is probably going to fill your subview with a bunch of NaNs,
  // so I'm not going to bother to implement it fast.
  // You can have slow NaNs.  They're fine too.
  for (uword c = 0; c < n_cols; ++c)
    for (uword r = 0; r < n_rows; ++r) {
      at(r, c) /= p.at(r, c);
    }
}

template <typename eT>
template <typename T1, typename gen_type>
inline typename enable_if2<is_same_type<typename T1::elem_type, eT>::value, void>::result
subview<eT>::operator=(const Gen<T1, gen_type>& in) {
  arma_extra_debug_sigprint();

  arma_debug_assert_same_size(n_rows, n_cols, in.n_rows, in.n_cols,
                              "copy into submatrix");

  in.apply(*this);
}

template <typename eT>
inline void subview<eT>::operator=(const std::initializer_list<eT>& list) {
  arma_extra_debug_sigprint();

  arma_debug_check((is_vec() == false), "copy into submatrix: size mismatch");

  const uword N = uword(list.size());

  if (n_rows == 1) {
    arma_debug_assert_same_size(1, n_cols, 1, N, "copy into submatrix");

    auto it = list.begin();

    for (uword ii = 0; ii < N; ++ii) {
      (*this).at(0, ii) = (*it);
      ++it;
    }
  } else if (n_cols == 1) {
    arma_debug_assert_same_size(n_rows, 1, N, 1, "copy into submatrix");

    arrayops::copy((*this).colptr(0), list.begin(), N);
  }
}

template <typename eT>
inline void subview<eT>::operator=(
    const std::initializer_list<std::initializer_list<eT> >& list) {
  arma_extra_debug_sigprint();

  const Mat<eT> tmp(list);

  (*this).operator=(tmp);
}

//! apply a functor to each element
template <typename eT>
template <typename functor>
inline void subview<eT>::for_each(functor F) {
  arma_extra_debug_sigprint();

  Mat<eT>& X = const_cast<Mat<eT>&>(m);

  if (n_rows == 1) {
    const uword urow = aux_row1;
    const uword start_col = aux_col1;
    const uword end_col_plus1 = start_col + n_cols;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol) {
      F(X.at(urow, ucol));
    }
  } else {
    const uword start_col = aux_col1;
    const uword start_row = aux_row1;

    const uword end_col_plus1 = start_col + n_cols;
    const uword end_row_plus1 = start_row + n_rows;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol)
      for (uword urow = start_row; urow < end_row_plus1; ++urow) {
        F(X.at(urow, ucol));
      }
  }
}

template <typename eT>
template <typename functor>
inline void subview<eT>::for_each(functor F) const {
  arma_extra_debug_sigprint();

  const Mat<eT>& X = m;

  if (n_rows == 1) {
    const uword urow = aux_row1;
    const uword start_col = aux_col1;
    const uword end_col_plus1 = start_col + n_cols;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol) {
      F(X.at(urow, ucol));
    }
  } else {
    const uword start_col = aux_col1;
    const uword start_row = aux_row1;

    const uword end_col_plus1 = start_col + n_cols;
    const uword end_row_plus1 = start_row + n_rows;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol)
      for (uword urow = start_row; urow < end_row_plus1; ++urow) {
        F(X.at(urow, ucol));
      }
  }
}

//! transform each element in the subview using a functor
template <typename eT>
template <typename functor>
inline void subview<eT>::transform(functor F) {
  arma_extra_debug_sigprint();

  Mat<eT>& X = const_cast<Mat<eT>&>(m);

  if (n_rows == 1) {
    const uword urow = aux_row1;
    const uword start_col = aux_col1;
    const uword end_col_plus1 = start_col + n_cols;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol) {
      X.at(urow, ucol) = eT(F(X.at(urow, ucol)));
    }
  } else {
    const uword start_col = aux_col1;
    const uword start_row = aux_row1;

    const uword end_col_plus1 = start_col + n_cols;
    const uword end_row_plus1 = start_row + n_rows;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol)
      for (uword urow = start_row; urow < end_row_plus1; ++urow) {
        X.at(urow, ucol) = eT(F(X.at(urow, ucol)));
      }
  }
}

//! imbue (fill) the subview with values provided by a functor
template <typename eT>
template <typename functor>
inline void subview<eT>::imbue(functor F) {
  arma_extra_debug_sigprint();

  Mat<eT>& X = const_cast<Mat<eT>&>(m);

  if (n_rows == 1) {
    const uword urow = aux_row1;
    const uword start_col = aux_col1;
    const uword end_col_plus1 = start_col + n_cols;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol) {
      X.at(urow, ucol) = eT(F());
    }
  } else {
    const uword start_col = aux_col1;
    const uword start_row = aux_row1;

    const uword end_col_plus1 = start_col + n_cols;
    const uword end_row_plus1 = start_row + n_rows;

    for (uword ucol = start_col; ucol < end_col_plus1; ++ucol)
      for (uword urow = start_row; urow < end_row_plus1; ++urow) {
        X.at(urow, ucol) = eT(F());
      }
  }
}

template <typename eT>
inline void subview<eT>::replace(const eT old_val, const eT new_val) {
  arma_extra_debug_sigprint();

  subview<eT>& s = *this;

  const uword s_n_cols = s.n_cols;
  const uword s_n_rows = s.n_rows;

  if (s_n_rows == 1) {
    Mat<eT>& A = const_cast<Mat<eT>&>(s.m);

    const uword A_n_rows = A.n_rows;

    eT* Aptr = &(A.at(s.aux_row1, s.aux_col1));

    if (arma_isnan(old_val)) {
      for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
        (*Aptr) = (arma_isnan(*Aptr)) ? new_val : (*Aptr);

        Aptr += A_n_rows;
      }
    } else {
      for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
        (*Aptr) = ((*Aptr) == old_val) ? new_val : (*Aptr);

        Aptr += A_n_rows;
      }
    }
  } else {
    for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
      arrayops::replace(s.colptr(ucol), s_n_rows, old_val, new_val);
    }
  }
}

template <typename eT>
inline void subview<eT>::clean(const typename get_pod_type<eT>::result threshold) {
  arma_extra_debug_sigprint();

  subview<eT>& s = *this;

  const uword s_n_cols = s.n_cols;
  const uword s_n_rows = s.n_rows;

  for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
    arrayops::clean(s.colptr(ucol), s_n_rows, threshold);
  }
}

template <typename eT>
inline void subview<eT>::clamp(const eT min_val, const eT max_val) {
  arma_extra_debug_sigprint();

  if (is_cx<eT>::no) {
    arma_debug_check((access::tmp_real(min_val) > access::tmp_real(max_val)),
                     "subview::clamp(): min_val must be less than max_val");
  } else {
    arma_debug_check((access::tmp_real(min_val) > access::tmp_real(max_val)),
                     "subview::clamp(): real(min_val) must be less than real(max_val)");
    arma_debug_check((access::tmp_imag(min_val) > access::tmp_imag(max_val)),
                     "subview::clamp(): imag(min_val) must be less than imag(max_val)");
  }

  subview<eT>& s = *this;

  const uword s_n_cols = s.n_cols;
  const uword s_n_rows = s.n_rows;

  for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
    arrayops::clamp(s.colptr(ucol), s_n_rows, min_val, max_val);
  }
}

template <typename eT>
inline void subview<eT>::fill(const eT val) {
  arma_extra_debug_sigprint();

  subview<eT>& s = *this;

  const uword s_n_cols = s.n_cols;
  const uword s_n_rows = s.n_rows;

  if (s_n_rows == 1) {
    Mat<eT>& A = const_cast<Mat<eT>&>(s.m);

    const uword A_n_rows = A.n_rows;

    eT* Aptr = &(A.at(s.aux_row1, s.aux_col1));

    uword jj;
    for (jj = 1; jj < s_n_cols; jj += 2) {
      (*Aptr) = val;
      Aptr += A_n_rows;
      (*Aptr) = val;
      Aptr += A_n_rows;
    }

    if ((jj - 1) < s_n_cols) {
      (*Aptr) = val;
    }
  } else {
    if ((s.aux_row1 == 0) && (s_n_rows == s.m.n_rows)) {
      arrayops::inplace_set(s.colptr(0), val, s.n_elem);
    } else {
      for (uword ucol = 0; ucol < s_n_cols; ++ucol) {
        arrayops::inplace_set(s.colptr(ucol), val, s_n_rows);
      }
    }
  }
}

template <typename eT>
inline void subview<eT>::zeros() {
  arma_extra_debug_sigprint();

  (*this).fill(eT(0));
}

template <typename eT>
inline void subview<eT>::ones() {
  arma_extra_debug_sigprint();

  (*this).fill(eT(1));
}

template <typename eT>
inline void subview<eT>::eye() {
  arma_extra_debug_sigprint();

  (*this).zeros();

  const uword N = (std::min)(n_rows, n_cols);

  for (uword ii = 0; ii < N; ++ii) {
    at(ii, ii) = eT(1);
  }
}

template <typename eT>
inline void subview<eT>::randu() {
  arma_extra_debug_sigprint();

  subview<eT>& s = (*this);

  const uword s_n_rows = s.n_rows;
  const uword s_n_cols = s.n_cols;

  if (s_n_rows == 1) {
    podarray<eT> tmp(s_n_cols);

    eT* tmp_mem = tmp.memptr();

    arma_rng::randu<eT>::fill(tmp_mem, s_n_cols);

    for (uword ii = 0; ii < s_n_cols; ++ii) {
      at(0, ii) = tmp_mem[ii];
    }
  } else {
    if ((s.aux_row1 == 0) && (s_n_rows == s.m.n_rows)) {
      arma_rng::randu<eT>::fill(s.colptr(0), s.n_elem);
    } else {
      for (uword ii = 0; ii < s_n_cols; ++ii) {
        arma_rng::randu<eT>::fill(s.colptr(ii), s_n_rows);
      }
    }
  }
}

template <typename eT>
inline void subview<eT>::randn() {
  arma_extra_debug_sigprint();

  subview<eT>& s = (*this);

  const uword s_n_rows = s.n_rows;
  const uword s_n_cols = s.n_cols;

  if (s_n_rows == 1) {
    podarray<eT> tmp(s_n_cols);

    eT* tmp_mem = tmp.memptr();

    arma_rng::randn<eT>::fill(tmp_mem, s_n_cols);

    for (uword ii = 0; ii < s_n_cols; ++ii) {
      at(0, ii) = tmp_mem[ii];
    }
  } else {
    if ((s.aux_row1 == 0) && (s_n_rows == s.m.n_rows)) {
      arma_rng::randn<eT>::fill(s.colptr(0), s.n_elem);
    } else {
      for (uword ii = 0; ii < s_n_cols; ++ii) {
        arma_rng::randn<eT>::fill(s.colptr(ii), s_n_rows);
      }
    }
  }
}

template <typename eT>
inline eT subview<eT>::at_alt(const uword ii) const {
  return operator[](ii);
}

template <typename eT>
inline eT& subview<eT>::operator[](const uword ii) {
  const uword in_col = ii / n_rows;
  const uword in_row = ii % n_rows;

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return access::rw((const_cast<Mat<eT>&>(m)).mem[index]);
}

template <typename eT>
inline eT subview<eT>::operator[](const uword ii) const {
  const uword in_col = ii / n_rows;
  const uword in_row = ii % n_rows;

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return m.mem[index];
}

template <typename eT>
inline eT& subview<eT>::operator()(const uword ii) {
  arma_debug_check_bounds((ii >= n_elem), "subview::operator(): index out of bounds");

  const uword in_col = ii / n_rows;
  const uword in_row = ii % n_rows;

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return access::rw((const_cast<Mat<eT>&>(m)).mem[index]);
}

template <typename eT>
inline eT subview<eT>::operator()(const uword ii) const {
  arma_debug_check_bounds((ii >= n_elem), "subview::operator(): index out of bounds");

  const uword in_col = ii / n_rows;
  const uword in_row = ii % n_rows;

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return m.mem[index];
}

template <typename eT>
inline eT& subview<eT>::operator()(const uword in_row, const uword in_col) {
  arma_debug_check_bounds(((in_row >= n_rows) || (in_col >= n_cols)),
                          "subview::operator(): index out of bounds");

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return access::rw((const_cast<Mat<eT>&>(m)).mem[index]);
}

template <typename eT>
inline eT subview<eT>::operator()(const uword in_row, const uword in_col) const {
  arma_debug_check_bounds(((in_row >= n_rows) || (in_col >= n_cols)),
                          "subview::operator(): index out of bounds");

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return m.mem[index];
}

template <typename eT>
inline eT& subview<eT>::at(const uword in_row, const uword in_col) {
  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return access::rw((const_cast<Mat<eT>&>(m)).mem[index]);
}

template <typename eT>
inline eT subview<eT>::at(const uword in_row, const uword in_col) const {
  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return m.mem[index];
}

template <typename eT>
inline eT& subview<eT>::front() {
  const uword index = aux_col1 * m.n_rows + aux_row1;

  return access::rw((const_cast<Mat<eT>&>(m)).mem[index]);
}

template <typename eT>
inline eT subview<eT>::front() const {
  const uword index = aux_col1 * m.n_rows + aux_row1;

  return m.mem[index];
}

template <typename eT>
inline eT& subview<eT>::back() {
  const uword in_row = n_rows - 1;
  const uword in_col = n_cols - 1;

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return access::rw((const_cast<Mat<eT>&>(m)).mem[index]);
}

template <typename eT>
inline eT subview<eT>::back() const {
  const uword in_row = n_rows - 1;
  const uword in_col = n_cols - 1;

  const uword index = (in_col + aux_col1) * m.n_rows + aux_row1 + in_row;

  return m.mem[index];
}

template <typename eT>
arma_inline eT* subview<eT>::colptr(const uword in_col) {
  return &access::rw(
      (const_cast<Mat<eT>&>(m)).mem[(in_col + aux_col1) * m.n_rows + aux_row1]);
}

template <typename eT>
arma_inline const eT* subview<eT>::colptr(const uword in_col) const {
  return &m.mem[(in_col + aux_col1) * m.n_rows + aux_row1];
}

template <typename eT>
template <typename eT2>
inline bool subview<eT>::check_overlap(const subview<eT2>& x) const {
  if (is_same_type<eT, eT2>::value == false) {
    return false;
  }

  const subview<eT>& s = (*this);

  if (void_ptr(&(s.m)) != void_ptr(&(x.m))) {
    return false;
  }

  if ((s.n_elem == 0) || (x.n_elem == 0)) {
    return false;
  }

  const uword s_row_start = s.aux_row1;
  const uword s_row_end_p1 = s_row_start + s.n_rows;

  const uword s_col_start = s.aux_col1;
  const uword s_col_end_p1 = s_col_start + s.n_cols;

  const uword x_row_start = x.aux_row1;
  const uword x_row_end_p1 = x_row_start + x.n_rows;

  const uword x_col_start = x.aux_col1;
  const uword x_col_end_p1 = x_col_start + x.n_cols;

  const bool outside_rows =
      ((x_row_start >= s_row_end_p1) || (s_row_start >= x_row_end_p1));
  const bool outside_cols =
      ((x_col_start >= s_col_end_p1) || (s_col_start >= x_col_end_p1));

  return ((outside_rows == false) && (outside_cols == false));
}

template <typename eT>
inline bool subview<eT>::is_vec() const {
  return ((n_rows == 1) || (n_cols == 1));
}

template <typename eT>
inline bool subview<eT>::is_finite() const {
  arma_extra_debug_sigprint();

  if (arma_config::fast_math_warn) {
    arma_debug_warn_level(
        1,
        "is_finite(): detection of non-finite values is not reliable in fast math mode");
  }

  const uword local_n_rows = n_rows;
  const uword local_n_cols = n_cols;

  for (uword ii = 0; ii < local_n_cols; ++ii) {
    if (arrayops::is_finite(colptr(ii), local_n_rows) == false) {
      return false;
    }
  }

  return true;
}

template <typename eT>
inline bool subview<eT>::is_zero(const typename get_pod_type<eT>::result tol) const {
  arma_extra_debug_sigprint();

  const uword local_n_rows = n_rows;
  const uword local_n_cols = n_cols;

  for (uword ii = 0; ii < local_n_cols; ++ii) {
    if (arrayops::is_zero(colptr(ii), local_n_rows, tol) == false) {
      return false;
    }
  }

  return true;
}

template <typename eT>
inline bool subview<eT>::has_inf() const {
  arma_extra_debug_sigprint();

  if (arma_config::fast_math_warn) {
    arma_debug_warn_level(
        1, "has_inf(): detection of non-finite values is not reliable in fast math mode");
  }

  const uword local_n_rows = n_rows;
  const uword local_n_cols = n_cols;

  for (uword ii = 0; ii < local_n_cols; ++ii) {
    if (arrayops::has_inf(colptr(ii), local_n_rows)) {
      return true;
    }
  }

  return false;
}

template <typename eT>
inline bool subview<eT>::has_nan() const {
  arma_extra_debug_sigprint();

  if (arma_config::fast_math_warn) {
    arma_debug_warn_level(
        1, "has_nan(): detection of non-finite values is not reliable in fast math mode");
  }

  const uword local_n_rows = n_rows;
  const uword local_n_cols = n_cols;

  for (uword ii = 0; ii < local_n_cols; ++ii) {
    if (arrayops::has_nan(colptr(ii), local_n_rows)) {
      return true;
    }
  }

  return false;
}

template <typename eT>
inline bool subview<eT>::has_nonfinite() const {
  arma_extra_debug_sigprint();

  if (arma_config::fast_math_warn) {
    arma_debug_warn_level(1,
                          "has_nonfinite(): detection of non-finite values is not "
                          "reliable in fast math mode");
  }

  const uword local_n_rows = n_rows;
  const uword local_n_cols = n_cols;

  for (uword ii = 0; ii < local_n_cols; ++ii) {
    if (arrayops::is_finite(colptr(ii), local_n_rows) == false) {
      return true;
    }
  }

  return false;
}

//! X = Y.submat(...)
template <typename eT>
inline void subview<eT>::extract(Mat<eT>& out, const subview<eT>& in) {
  arma_extra_debug_sigprint();

  // NOTE: we're assuming that the matrix has already been set to the correct size and
  // there is no aliasing; size setting and alias checking is done by either the Mat
  // contructor or operator=()

  const uword n_rows = in.n_rows;  // number of rows in the subview
  const uword n_cols = in.n_cols;  // number of columns in the subview

  arma_extra_debug_print(
      arma_str::format(
          "out.n_rows = %u   out.n_cols = %u    in.m.n_rows = %u  in.m.n_cols = %u") %
      out.n_rows % out.n_cols % in.m.n_rows % in.m.n_cols);

  if (in.is_vec()) {
    if (n_cols == 1)  // a column vector
    {
      arma_extra_debug_print("subview::extract(): copying col (going across rows)");

      // in.colptr(0) the first column of the subview, taking into account any row offset
      arrayops::copy(out.memptr(), in.colptr(0), n_rows);
    } else if (n_rows == 1)  // a row vector
    {
      arma_extra_debug_print("subview::extract(): copying row (going across columns)");

      eT* out_mem = out.memptr();

      const uword X_n_rows = in.m.n_rows;

      const eT* Xptr = &(in.m.at(in.aux_row1, in.aux_col1));

      uword j;

      for (j = 1; j < n_cols; j += 2) {
        const eT tmp1 = (*Xptr);
        Xptr += X_n_rows;
        const eT tmp2 = (*Xptr);
        Xptr += X_n_rows;

        (*out_mem) = tmp1;
        out_mem++;
        (*out_mem) = tmp2;
        out_mem++;
      }

      if ((j - 1) < n_cols) {
        (*out_mem) = (*Xptr);
      }
    }
  } else  // general submatrix
  {
    arma_extra_debug_print("subview::extract(): general submatrix");

    if ((in.aux_row1 == 0) && (n_rows == in.m.n_rows)) {
      arrayops::copy(out.memptr(), in.colptr(0), in.n_elem);
    } else {
      for (uword col = 0; col < n_cols; ++col) {
        arrayops::copy(out.colptr(col), in.colptr(col), n_rows);
      }
    }
  }
}

//! X += Y.submat(...)
template <typename eT>
inline void subview<eT>::plus_inplace(Mat<eT>& out, const subview<eT>& in) {
  arma_extra_debug_sigprint();

  arma_debug_assert_same_size(out, in, "addition");

  const uword n_rows = in.n_rows;
  const uword n_cols = in.n_cols;

  if (n_rows == 1) {
    eT* out_mem = out.memptr();

    const Mat<eT>& X = in.m;

    const uword row = in.aux_row1;
    const uword start_col = in.aux_col1;

    uword i, j;
    for (i = 0, j = 1; j < n_cols; i += 2, j += 2) {
      const eT tmp1 = X.at(row, start_col + i);
      const eT tmp2 = X.at(row, start_col + j);

      out_mem[i] += tmp1;
      out_mem[j] += tmp2;
    }

    if (i < n_cols) {
      out_mem[i] += X.at(row, start_col + i);
    }
  } else {
    for (uword col = 0; col < n_cols; ++col) {
      arrayops::inplace_plus(out.colptr(col), in.colptr(col), n_rows);
    }
  }
}

//! X -= Y.submat(...)
template <typename eT>
inline void subview<eT>::minus_inplace(Mat<eT>& out, const subview<eT>& in) {
  arma_extra_debug_sigprint();

  arma_debug_assert_same_size(out, in, "subtraction");

  const uword n_rows = in.n_rows;
  const uword n_cols = in.n_cols;

  if (n_rows == 1) {
    eT* out_mem = out.memptr();

    const Mat<eT>& X = in.m;

    const uword row = in.aux_row1;
    const uword start_col = in.aux_col1;

    uword i, j;
    for (i = 0, j = 1; j < n_cols; i += 2, j += 2) {
      const eT tmp1 = X.at(row, start_col + i);
      const eT tmp2 = X.at(row, start_col + j);

      out_mem[i] -= tmp1;
      out_mem[j] -= tmp2;
    }

    if (i < n_cols) {
      out_mem[i] -= X.at(row, start_col + i);
    }
  } else {
    for (uword col = 0; col < n_cols; ++col) {
      arrayops::inplace_minus(out.colptr(col), in.colptr(col), n_rows);
    }
  }
}

//! X %= Y.submat(...)
template <typename eT>
inline void subview<eT>::schur_inplace(Mat<eT>& out, const subview<eT>& in) {
  arma_extra_debug_sigprint();

  arma_debug_assert_same_size(out, in, "element-wise multiplication");

  const uword n_rows = in.n_rows;
  const uword n_cols = in.n_cols;

  if (n_rows == 1) {
    eT* out_mem = out.memptr();

    const Mat<eT>& X = in.m;

    const uword row = in.aux_row1;
    const uword start_col = in.aux_col1;

    uword i, j;
    for (i = 0, j = 1; j < n_cols; i += 2, j += 2) {
      const eT tmp1 = X.at(row, start_col + i);
      const eT tmp2 = X.at(row, start_col + j);

      out_mem[i] *= tmp1;
      out_mem[j] *= tmp2;
    }

    if (i < n_cols) {
      out_mem[i] *= X.at(row, start_col + i);
    }
  } else {
    for (uword col = 0; col < n_cols; ++col) {
      arrayops::inplace_mul(out.colptr(col), in.colptr(col), n_rows);
    }
  }
}

//! X /= Y.submat(...)
template <typename eT>
inline void subview<eT>::div_inplace(Mat<eT>& out, const subview<eT>& in) {
  arma_extra_debug_sigprint();

  arma_debug_assert_same_size(out, in, "element-wise division");

  const uword n_rows = in.n_rows;
  const uword n_cols = in.n_cols;

  if (n_rows == 1) {
    eT* out_mem = out.memptr();

    const Mat<eT>& X = in.m;

    const uword row = in.aux_row1;
    const uword start_col = in.aux_col1;

    uword i, j;
    for (i = 0, j = 1; j < n_cols; i += 2, j += 2) {
      const eT tmp1 = X.at(row, start_col + i);
      const eT tmp2 = X.at(row, start_col + j);

      out_mem[i] /= tmp1;
      out_mem[j] /= tmp2;
    }

    if (i < n_cols) {
      out_mem[i] /= X.at(row, start_col + i);
    }
  } else {
    for (uword col = 0; col < n_cols; ++col) {
      arrayops::inplace_div(out.colptr(col), in.colptr(col), n_rows);
    }
  }
}

//! creation of subview (row vector)
template <typename eT>
inline subview_row<eT> subview<eT>::row(const uword row_num) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(row_num >= n_rows, "subview::row(): out of bounds");

  const uword base_row = aux_row1 + row_num;

  return subview_row<eT>(m, base_row, aux_col1, n_cols);
}

//! creation of subview (row vector)
template <typename eT>
inline const subview_row<eT> subview<eT>::row(const uword row_num) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(row_num >= n_rows, "subview::row(): out of bounds");

  const uword base_row = aux_row1 + row_num;

  return subview_row<eT>(m, base_row, aux_col1, n_cols);
}

template <typename eT>
inline subview_row<eT> subview<eT>::operator()(const uword row_num,
                                               const span& col_span) {
  arma_extra_debug_sigprint();

  const bool col_all = col_span.whole;

  const uword local_n_cols = n_cols;

  const uword in_col1 = col_all ? 0 : col_span.a;
  const uword in_col2 = col_span.b;
  const uword submat_n_cols = col_all ? local_n_cols : in_col2 - in_col1 + 1;

  const uword base_col1 = aux_col1 + in_col1;
  const uword base_row = aux_row1 + row_num;

  arma_debug_check_bounds(
      (row_num >= n_rows) ||
          (col_all ? false : ((in_col1 > in_col2) || (in_col2 >= local_n_cols))),
      "subview::operator(): indices out of bounds or incorrectly used");

  return subview_row<eT>(m, base_row, base_col1, submat_n_cols);
}

template <typename eT>
inline const subview_row<eT> subview<eT>::operator()(const uword row_num,
                                                     const span& col_span) const {
  arma_extra_debug_sigprint();

  const bool col_all = col_span.whole;

  const uword local_n_cols = n_cols;

  const uword in_col1 = col_all ? 0 : col_span.a;
  const uword in_col2 = col_span.b;
  const uword submat_n_cols = col_all ? local_n_cols : in_col2 - in_col1 + 1;

  const uword base_col1 = aux_col1 + in_col1;
  const uword base_row = aux_row1 + row_num;

  arma_debug_check_bounds(
      (row_num >= n_rows) ||
          (col_all ? false : ((in_col1 > in_col2) || (in_col2 >= local_n_cols))),
      "subview::operator(): indices out of bounds or incorrectly used");

  return subview_row<eT>(m, base_row, base_col1, submat_n_cols);
}

//! creation of subview (column vector)
template <typename eT>
inline subview_col<eT> subview<eT>::col(const uword col_num) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(col_num >= n_cols, "subview::col(): out of bounds");

  const uword base_col = aux_col1 + col_num;

  return subview_col<eT>(m, base_col, aux_row1, n_rows);
}

//! creation of subview (column vector)
template <typename eT>
inline const subview_col<eT> subview<eT>::col(const uword col_num) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(col_num >= n_cols, "subview::col(): out of bounds");

  const uword base_col = aux_col1 + col_num;

  return subview_col<eT>(m, base_col, aux_row1, n_rows);
}

template <typename eT>
inline subview_col<eT> subview<eT>::operator()(const span& row_span,
                                               const uword col_num) {
  arma_extra_debug_sigprint();

  const bool row_all = row_span.whole;

  const uword local_n_rows = n_rows;

  const uword in_row1 = row_all ? 0 : row_span.a;
  const uword in_row2 = row_span.b;
  const uword submat_n_rows = row_all ? local_n_rows : in_row2 - in_row1 + 1;

  const uword base_row1 = aux_row1 + in_row1;
  const uword base_col = aux_col1 + col_num;

  arma_debug_check_bounds(
      (col_num >= n_cols) ||
          (row_all ? false : ((in_row1 > in_row2) || (in_row2 >= local_n_rows))),
      "subview::operator(): indices out of bounds or incorrectly used");

  return subview_col<eT>(m, base_col, base_row1, submat_n_rows);
}

template <typename eT>
inline const subview_col<eT> subview<eT>::operator()(const span& row_span,
                                                     const uword col_num) const {
  arma_extra_debug_sigprint();

  const bool row_all = row_span.whole;

  const uword local_n_rows = n_rows;

  const uword in_row1 = row_all ? 0 : row_span.a;
  const uword in_row2 = row_span.b;
  const uword submat_n_rows = row_all ? local_n_rows : in_row2 - in_row1 + 1;

  const uword base_row1 = aux_row1 + in_row1;
  const uword base_col = aux_col1 + col_num;

  arma_debug_check_bounds(
      (col_num >= n_cols) ||
          (row_all ? false : ((in_row1 > in_row2) || (in_row2 >= local_n_rows))),
      "subview::operator(): indices out of bounds or incorrectly used");

  return subview_col<eT>(m, base_col, base_row1, submat_n_rows);
}

//! create a Col object which uses memory from an existing matrix object.
//! this approach is currently not alias safe
//! and does not take into account that the parent matrix object could be deleted.
//! if deleted memory is accessed by the created Col object,
//! it will cause memory corruption and/or a crash
template <typename eT>
inline Col<eT> subview<eT>::unsafe_col(const uword col_num) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(col_num >= n_cols, "subview::unsafe_col(): out of bounds");

  return Col<eT>(colptr(col_num), n_rows, false, true);
}

//! create a Col object which uses memory from an existing matrix object.
//! this approach is currently not alias safe
//! and does not take into account that the parent matrix object could be deleted.
//! if deleted memory is accessed by the created Col object,
//! it will cause memory corruption and/or a crash
template <typename eT>
inline const Col<eT> subview<eT>::unsafe_col(const uword col_num) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(col_num >= n_cols, "subview::unsafe_col(): out of bounds");

  return Col<eT>(const_cast<eT*>(colptr(col_num)), n_rows, false, true);
}

//! creation of subview (submatrix comprised of specified row vectors)
template <typename eT>
inline subview<eT> subview<eT>::rows(const uword in_row1, const uword in_row2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_row1 > in_row2) || (in_row2 >= n_rows),
                          "subview::rows(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;
  const uword base_row1 = aux_row1 + in_row1;

  return subview<eT>(m, base_row1, aux_col1, subview_n_rows, n_cols);
}

//! creation of subview (submatrix comprised of specified row vectors)
template <typename eT>
inline const subview<eT> subview<eT>::rows(const uword in_row1,
                                           const uword in_row2) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_row1 > in_row2) || (in_row2 >= n_rows),
                          "subview::rows(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;
  const uword base_row1 = aux_row1 + in_row1;

  return subview<eT>(m, base_row1, aux_col1, subview_n_rows, n_cols);
}

//! creation of subview (submatrix comprised of specified column vectors)
template <typename eT>
inline subview<eT> subview<eT>::cols(const uword in_col1, const uword in_col2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_col1 > in_col2) || (in_col2 >= n_cols),
                          "subview::cols(): indices out of bounds or incorrectly used");

  const uword subview_n_cols = in_col2 - in_col1 + 1;
  const uword base_col1 = aux_col1 + in_col1;

  return subview<eT>(m, aux_row1, base_col1, n_rows, subview_n_cols);
}

//! creation of subview (submatrix comprised of specified column vectors)
template <typename eT>
inline const subview<eT> subview<eT>::cols(const uword in_col1,
                                           const uword in_col2) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_col1 > in_col2) || (in_col2 >= n_cols),
                          "subview::cols(): indices out of bounds or incorrectly used");

  const uword subview_n_cols = in_col2 - in_col1 + 1;
  const uword base_col1 = aux_col1 + in_col1;

  return subview<eT>(m, aux_row1, base_col1, n_rows, subview_n_cols);
}

//! creation of subview (submatrix)
template <typename eT>
inline subview<eT> subview<eT>::submat(const uword in_row1, const uword in_col1,
                                       const uword in_row2, const uword in_col2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_row1 > in_row2) || (in_col1 > in_col2) ||
                              (in_row2 >= n_rows) || (in_col2 >= n_cols),
                          "subview::submat(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;
  const uword subview_n_cols = in_col2 - in_col1 + 1;

  const uword base_row1 = aux_row1 + in_row1;
  const uword base_col1 = aux_col1 + in_col1;

  return subview<eT>(m, base_row1, base_col1, subview_n_rows, subview_n_cols);
}

//! creation of subview (generic submatrix)
template <typename eT>
inline const subview<eT> subview<eT>::submat(const uword in_row1, const uword in_col1,
                                             const uword in_row2,
                                             const uword in_col2) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_row1 > in_row2) || (in_col1 > in_col2) ||
                              (in_row2 >= n_rows) || (in_col2 >= n_cols),
                          "subview::submat(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;
  const uword subview_n_cols = in_col2 - in_col1 + 1;

  const uword base_row1 = aux_row1 + in_row1;
  const uword base_col1 = aux_col1 + in_col1;

  return subview<eT>(m, base_row1, base_col1, subview_n_rows, subview_n_cols);
}

//! creation of subview (submatrix)
template <typename eT>
inline subview<eT> subview<eT>::submat(const span& row_span, const span& col_span) {
  arma_extra_debug_sigprint();

  const bool row_all = row_span.whole;
  const bool col_all = col_span.whole;

  const uword local_n_rows = n_rows;
  const uword local_n_cols = n_cols;

  const uword in_row1 = row_all ? 0 : row_span.a;
  const uword in_row2 = row_span.b;
  const uword submat_n_rows = row_all ? local_n_rows : in_row2 - in_row1 + 1;

  const uword in_col1 = col_all ? 0 : col_span.a;
  const uword in_col2 = col_span.b;
  const uword submat_n_cols = col_all ? local_n_cols : in_col2 - in_col1 + 1;

  arma_debug_check_bounds(
      (row_all ? false : ((in_row1 > in_row2) || (in_row2 >= local_n_rows))) ||
          (col_all ? false : ((in_col1 > in_col2) || (in_col2 >= local_n_cols))),
      "subview::submat(): indices out of bounds or incorrectly used");

  const uword base_row1 = aux_row1 + in_row1;
  const uword base_col1 = aux_col1 + in_col1;

  return subview<eT>(m, base_row1, base_col1, submat_n_rows, submat_n_cols);
}

//! creation of subview (generic submatrix)
template <typename eT>
inline const subview<eT> subview<eT>::submat(const span& row_span,
                                             const span& col_span) const {
  arma_extra_debug_sigprint();

  const bool row_all = row_span.whole;
  const bool col_all = col_span.whole;

  const uword local_n_rows = n_rows;
  const uword local_n_cols = n_cols;

  const uword in_row1 = row_all ? 0 : row_span.a;
  const uword in_row2 = row_span.b;
  const uword submat_n_rows = row_all ? local_n_rows : in_row2 - in_row1 + 1;

  const uword in_col1 = col_all ? 0 : col_span.a;
  const uword in_col2 = col_span.b;
  const uword submat_n_cols = col_all ? local_n_cols : in_col2 - in_col1 + 1;

  arma_debug_check_bounds(
      (row_all ? false : ((in_row1 > in_row2) || (in_row2 >= local_n_rows))) ||
          (col_all ? false : ((in_col1 > in_col2) || (in_col2 >= local_n_cols))),
      "subview::submat(): indices out of bounds or incorrectly used");

  const uword base_row1 = aux_row1 + in_row1;
  const uword base_col1 = aux_col1 + in_col1;

  return subview<eT>(m, base_row1, base_col1, submat_n_rows, submat_n_cols);
}

template <typename eT>
inline subview<eT> subview<eT>::operator()(const span& row_span, const span& col_span) {
  arma_extra_debug_sigprint();

  return (*this).submat(row_span, col_span);
}

template <typename eT>
inline const subview<eT> subview<eT>::operator()(const span& row_span,
                                                 const span& col_span) const {
  arma_extra_debug_sigprint();

  return (*this).submat(row_span, col_span);
}

template <typename eT>
inline subview_each1<subview<eT>, 0> subview<eT>::each_col() {
  arma_extra_debug_sigprint();

  return subview_each1<subview<eT>, 0>(*this);
}

template <typename eT>
inline subview_each1<subview<eT>, 1> subview<eT>::each_row() {
  arma_extra_debug_sigprint();

  return subview_each1<subview<eT>, 1>(*this);
}

template <typename eT>
template <typename T1>
inline subview_each2<subview<eT>, 0, T1> subview<eT>::each_col(
    const Base<uword, T1>& indices) {
  arma_extra_debug_sigprint();

  return subview_each2<subview<eT>, 0, T1>(*this, indices);
}

template <typename eT>
template <typename T1>
inline subview_each2<subview<eT>, 1, T1> subview<eT>::each_row(
    const Base<uword, T1>& indices) {
  arma_extra_debug_sigprint();

  return subview_each2<subview<eT>, 1, T1>(*this, indices);
}

//! apply a lambda function to each column, where each column is interpreted as a column
//! vector
template <typename eT>
inline void subview<eT>::each_col(const std::function<void(Col<eT>&)>& F) {
  arma_extra_debug_sigprint();

  for (uword ii = 0; ii < n_cols; ++ii) {
    Col<eT> tmp(colptr(ii), n_rows, false, true);
    F(tmp);
  }
}

template <typename eT>
inline void subview<eT>::each_col(const std::function<void(const Col<eT>&)>& F) const {
  arma_extra_debug_sigprint();

  for (uword ii = 0; ii < n_cols; ++ii) {
    const Col<eT> tmp(colptr(ii), n_rows, false, true);
    F(tmp);
  }
}

//! apply a lambda function to each row, where each row is interpreted as a row vector
template <typename eT>
inline void subview<eT>::each_row(const std::function<void(Row<eT>&)>& F) {
  arma_extra_debug_sigprint();

  podarray<eT> array1(n_cols);
  podarray<eT> array2(n_cols);

  Row<eT> tmp1(array1.memptr(), n_cols, false, true);
  Row<eT> tmp2(array2.memptr(), n_cols, false, true);

  eT* tmp1_mem = tmp1.memptr();
  eT* tmp2_mem = tmp2.memptr();

  uword ii, jj;

  for (ii = 0, jj = 1; jj < n_rows; ii += 2, jj += 2) {
    for (uword col_id = 0; col_id < n_cols; ++col_id) {
      const eT* col_mem = colptr(col_id);

      tmp1_mem[col_id] = col_mem[ii];
      tmp2_mem[col_id] = col_mem[jj];
    }

    F(tmp1);
    F(tmp2);

    for (uword col_id = 0; col_id < n_cols; ++col_id) {
      eT* col_mem = colptr(col_id);

      col_mem[ii] = tmp1_mem[col_id];
      col_mem[jj] = tmp2_mem[col_id];
    }
  }

  if (ii < n_rows) {
    tmp1 = (*this).row(ii);

    F(tmp1);

    (*this).row(ii) = tmp1;
  }
}

template <typename eT>
inline void subview<eT>::each_row(const std::function<void(const Row<eT>&)>& F) const {
  arma_extra_debug_sigprint();

  podarray<eT> array1(n_cols);
  podarray<eT> array2(n_cols);

  Row<eT> tmp1(array1.memptr(), n_cols, false, true);
  Row<eT> tmp2(array2.memptr(), n_cols, false, true);

  eT* tmp1_mem = tmp1.memptr();
  eT* tmp2_mem = tmp2.memptr();

  uword ii, jj;

  for (ii = 0, jj = 1; jj < n_rows; ii += 2, jj += 2) {
    for (uword col_id = 0; col_id < n_cols; ++col_id) {
      const eT* col_mem = colptr(col_id);

      tmp1_mem[col_id] = col_mem[ii];
      tmp2_mem[col_id] = col_mem[jj];
    }

    F(tmp1);
    F(tmp2);
  }

  if (ii < n_rows) {
    tmp1 = (*this).row(ii);

    F(tmp1);
  }
}

//! creation of diagview (diagonal)
template <typename eT>
inline diagview<eT> subview<eT>::diag(const sword in_id) {
  arma_extra_debug_sigprint();

  const uword row_offset = (in_id < 0) ? uword(-in_id) : 0;
  const uword col_offset = (in_id > 0) ? uword(in_id) : 0;

  arma_debug_check_bounds(((row_offset > 0) && (row_offset >= n_rows)) ||
                              ((col_offset > 0) && (col_offset >= n_cols)),
                          "subview::diag(): requested diagonal out of bounds");

  const uword len = (std::min)(n_rows - row_offset, n_cols - col_offset);

  const uword base_row_offset = aux_row1 + row_offset;
  const uword base_col_offset = aux_col1 + col_offset;

  return diagview<eT>(m, base_row_offset, base_col_offset, len);
}

//! creation of diagview (diagonal)
template <typename eT>
inline const diagview<eT> subview<eT>::diag(const sword in_id) const {
  arma_extra_debug_sigprint();

  const uword row_offset = uword((in_id < 0) ? -in_id : 0);
  const uword col_offset = uword((in_id > 0) ? in_id : 0);

  arma_debug_check_bounds(((row_offset > 0) && (row_offset >= n_rows)) ||
                              ((col_offset > 0) && (col_offset >= n_cols)),
                          "subview::diag(): requested diagonal out of bounds");

  const uword len = (std::min)(n_rows - row_offset, n_cols - col_offset);

  const uword base_row_offset = aux_row1 + row_offset;
  const uword base_col_offset = aux_col1 + col_offset;

  return diagview<eT>(m, base_row_offset, base_col_offset, len);
}

template <typename eT>
inline void subview<eT>::swap_rows(const uword in_row1, const uword in_row2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_row1 >= n_rows) || (in_row2 >= n_rows),
                          "subview::swap_rows(): out of bounds");

  eT* mem = (const_cast<Mat<eT>&>(m)).memptr();

  if (n_elem > 0) {
    const uword m_n_rows = m.n_rows;

    for (uword ucol = 0; ucol < n_cols; ++ucol) {
      const uword offset = (aux_col1 + ucol) * m_n_rows;
      const uword pos1 = aux_row1 + in_row1 + offset;
      const uword pos2 = aux_row1 + in_row2 + offset;

      std::swap(access::rw(mem[pos1]), access::rw(mem[pos2]));
    }
  }
}

template <typename eT>
inline void subview<eT>::swap_cols(const uword in_col1, const uword in_col2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((in_col1 >= n_cols) || (in_col2 >= n_cols),
                          "subview::swap_cols(): out of bounds");

  if (n_elem > 0) {
    eT* ptr1 = colptr(in_col1);
    eT* ptr2 = colptr(in_col2);

    for (uword urow = 0; urow < n_rows; ++urow) {
      std::swap(ptr1[urow], ptr2[urow]);
    }
  }
}

template <typename eT>
inline typename subview<eT>::iterator subview<eT>::begin() {
  return iterator(*this, aux_row1, aux_col1);
}

template <typename eT>
inline typename subview<eT>::const_iterator subview<eT>::begin() const {
  return const_iterator(*this, aux_row1, aux_col1);
}

template <typename eT>
inline typename subview<eT>::const_iterator subview<eT>::cbegin() const {
  return const_iterator(*this, aux_row1, aux_col1);
}

template <typename eT>
inline typename subview<eT>::iterator subview<eT>::end() {
  return iterator(*this, aux_row1, aux_col1 + n_cols);
}

template <typename eT>
inline typename subview<eT>::const_iterator subview<eT>::end() const {
  return const_iterator(*this, aux_row1, aux_col1 + n_cols);
}

template <typename eT>
inline typename subview<eT>::const_iterator subview<eT>::cend() const {
  return const_iterator(*this, aux_row1, aux_col1 + n_cols);
}

//
//
//

template <typename eT>
inline subview<eT>::iterator::iterator()
    : M(nullptr),
      current_ptr(nullptr),
      current_row(0),
      current_col(0),
      aux_row1(0),
      aux_row2_p1(0) {
  arma_extra_debug_sigprint();
  // Technically this iterator is invalid (it does not point to a valid element)
}

template <typename eT>
inline subview<eT>::iterator::iterator(const iterator& X)
    : M(X.M),
      current_ptr(X.current_ptr),
      current_row(X.current_row),
      current_col(X.current_col),
      aux_row1(X.aux_row1),
      aux_row2_p1(X.aux_row2_p1) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview<eT>::iterator::iterator(subview<eT>& in_sv, const uword in_row,
                                       const uword in_col)
    : M(&(const_cast<Mat<eT>&>(in_sv.m))),
      current_ptr(&(M->at(in_row, in_col))),
      current_row(in_row),
      current_col(in_col),
      aux_row1(in_sv.aux_row1),
      aux_row2_p1(in_sv.aux_row1 + in_sv.n_rows) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline eT& subview<eT>::iterator::operator*() {
  return (*current_ptr);
}

template <typename eT>
inline typename subview<eT>::iterator& subview<eT>::iterator::operator++() {
  current_row++;

  if (current_row == aux_row2_p1) {
    current_row = aux_row1;
    current_col++;

    current_ptr = &((*M).at(current_row, current_col));
  } else {
    current_ptr++;
  }

  return *this;
}

template <typename eT>
inline typename subview<eT>::iterator subview<eT>::iterator::operator++(int) {
  typename subview<eT>::iterator temp(*this);

  ++(*this);

  return temp;
}

template <typename eT>
inline bool subview<eT>::iterator::operator==(const iterator& rhs) const {
  return (current_ptr == rhs.current_ptr);
}

template <typename eT>
inline bool subview<eT>::iterator::operator!=(const iterator& rhs) const {
  return (current_ptr != rhs.current_ptr);
}

template <typename eT>
inline bool subview<eT>::iterator::operator==(const const_iterator& rhs) const {
  return (current_ptr == rhs.current_ptr);
}

template <typename eT>
inline bool subview<eT>::iterator::operator!=(const const_iterator& rhs) const {
  return (current_ptr != rhs.current_ptr);
}

//
//
//

template <typename eT>
inline subview<eT>::const_iterator::const_iterator()
    : M(nullptr),
      current_ptr(nullptr),
      current_row(0),
      current_col(0),
      aux_row1(0),
      aux_row2_p1(0) {
  arma_extra_debug_sigprint();
  // Technically this iterator is invalid (it does not point to a valid element)
}

template <typename eT>
inline subview<eT>::const_iterator::const_iterator(const iterator& X)
    : M(X.M),
      current_ptr(X.current_ptr),
      current_row(X.current_row),
      current_col(X.current_col),
      aux_row1(X.aux_row1),
      aux_row2_p1(X.aux_row2_p1) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview<eT>::const_iterator::const_iterator(const const_iterator& X)
    : M(X.M),
      current_ptr(X.current_ptr),
      current_row(X.current_row),
      current_col(X.current_col),
      aux_row1(X.aux_row1),
      aux_row2_p1(X.aux_row2_p1) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview<eT>::const_iterator::const_iterator(const subview<eT>& in_sv,
                                                   const uword in_row, const uword in_col)
    : M(&(in_sv.m)),
      current_ptr(&(M->at(in_row, in_col))),
      current_row(in_row),
      current_col(in_col),
      aux_row1(in_sv.aux_row1),
      aux_row2_p1(in_sv.aux_row1 + in_sv.n_rows) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline const eT& subview<eT>::const_iterator::operator*() {
  return (*current_ptr);
}

template <typename eT>
inline typename subview<eT>::const_iterator& subview<eT>::const_iterator::operator++() {
  current_row++;

  if (current_row == aux_row2_p1) {
    current_row = aux_row1;
    current_col++;

    current_ptr = &((*M).at(current_row, current_col));
  } else {
    current_ptr++;
  }

  return *this;
}

template <typename eT>
inline typename subview<eT>::const_iterator subview<eT>::const_iterator::operator++(int) {
  typename subview<eT>::const_iterator temp(*this);

  ++(*this);

  return temp;
}

template <typename eT>
inline bool subview<eT>::const_iterator::operator==(const iterator& rhs) const {
  return (current_ptr == rhs.current_ptr);
}

template <typename eT>
inline bool subview<eT>::const_iterator::operator!=(const iterator& rhs) const {
  return (current_ptr != rhs.current_ptr);
}

template <typename eT>
inline bool subview<eT>::const_iterator::operator==(const const_iterator& rhs) const {
  return (current_ptr == rhs.current_ptr);
}

template <typename eT>
inline bool subview<eT>::const_iterator::operator!=(const const_iterator& rhs) const {
  return (current_ptr != rhs.current_ptr);
}

//
//
//

template <typename eT>
inline subview<eT>::row_iterator::row_iterator()
    : M(nullptr), current_row(0), current_col(0), aux_col1(0), aux_col2_p1(0) {
  arma_extra_debug_sigprint();
  // Technically this iterator is invalid (it does not point to a valid element)
}

template <typename eT>
inline subview<eT>::row_iterator::row_iterator(const row_iterator& X)
    : M(X.M),
      current_row(X.current_row),
      current_col(X.current_col),
      aux_col1(X.aux_col1),
      aux_col2_p1(X.aux_col2_p1) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview<eT>::row_iterator::row_iterator(subview<eT>& in_sv, const uword in_row,
                                               const uword in_col)
    : M(&(const_cast<Mat<eT>&>(in_sv.m))),
      current_row(in_row),
      current_col(in_col),
      aux_col1(in_sv.aux_col1),
      aux_col2_p1(in_sv.aux_col1 + in_sv.n_cols) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline eT& subview<eT>::row_iterator::operator*() {
  return M->at(current_row, current_col);
}

template <typename eT>
inline typename subview<eT>::row_iterator& subview<eT>::row_iterator::operator++() {
  current_col++;

  if (current_col == aux_col2_p1) {
    current_col = aux_col1;
    current_row++;
  }

  return *this;
}

template <typename eT>
inline typename subview<eT>::row_iterator subview<eT>::row_iterator::operator++(int) {
  typename subview<eT>::row_iterator temp(*this);

  ++(*this);

  return temp;
}

template <typename eT>
inline bool subview<eT>::row_iterator::operator==(const row_iterator& rhs) const {
  return ((current_row == rhs.current_row) && (current_col == rhs.current_col));
}

template <typename eT>
inline bool subview<eT>::row_iterator::operator!=(const row_iterator& rhs) const {
  return ((current_row != rhs.current_row) || (current_col != rhs.current_col));
}

template <typename eT>
inline bool subview<eT>::row_iterator::operator==(const const_row_iterator& rhs) const {
  return ((current_row == rhs.current_row) && (current_col == rhs.current_col));
}

template <typename eT>
inline bool subview<eT>::row_iterator::operator!=(const const_row_iterator& rhs) const {
  return ((current_row != rhs.current_row) || (current_col != rhs.current_col));
}

//
//
//

template <typename eT>
inline subview<eT>::const_row_iterator::const_row_iterator()
    : M(nullptr), current_row(0), current_col(0), aux_col1(0), aux_col2_p1(0) {
  arma_extra_debug_sigprint();
  // Technically this iterator is invalid (it does not point to a valid element)
}

template <typename eT>
inline subview<eT>::const_row_iterator::const_row_iterator(const row_iterator& X)
    : M(X.M),
      current_row(X.current_row),
      current_col(X.current_col),
      aux_col1(X.aux_col1),
      aux_col2_p1(X.aux_col2_p1) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview<eT>::const_row_iterator::const_row_iterator(const const_row_iterator& X)
    : M(X.M),
      current_row(X.current_row),
      current_col(X.current_col),
      aux_col1(X.aux_col1),
      aux_col2_p1(X.aux_col2_p1) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview<eT>::const_row_iterator::const_row_iterator(const subview<eT>& in_sv,
                                                           const uword in_row,
                                                           const uword in_col)
    : M(&(in_sv.m)),
      current_row(in_row),
      current_col(in_col),
      aux_col1(in_sv.aux_col1),
      aux_col2_p1(in_sv.aux_col1 + in_sv.n_cols) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline const eT& subview<eT>::const_row_iterator::operator*() const {
  return M->at(current_row, current_col);
}

template <typename eT>
inline typename subview<eT>::const_row_iterator&
subview<eT>::const_row_iterator::operator++() {
  current_col++;

  if (current_col == aux_col2_p1) {
    current_col = aux_col1;
    current_row++;
  }

  return *this;
}

template <typename eT>
inline typename subview<eT>::const_row_iterator
subview<eT>::const_row_iterator::operator++(int) {
  typename subview<eT>::const_row_iterator temp(*this);

  ++(*this);

  return temp;
}

template <typename eT>
inline bool subview<eT>::const_row_iterator::operator==(const row_iterator& rhs) const {
  return ((current_row == rhs.current_row) && (current_col == rhs.current_col));
}

template <typename eT>
inline bool subview<eT>::const_row_iterator::operator!=(const row_iterator& rhs) const {
  return ((current_row != rhs.current_row) || (current_col != rhs.current_col));
}

template <typename eT>
inline bool subview<eT>::const_row_iterator::operator==(
    const const_row_iterator& rhs) const {
  return ((current_row == rhs.current_row) && (current_col == rhs.current_col));
}

template <typename eT>
inline bool subview<eT>::const_row_iterator::operator!=(
    const const_row_iterator& rhs) const {
  return ((current_row != rhs.current_row) || (current_col != rhs.current_col));
}

//
//
//

template <typename eT>
inline subview_col<eT>::subview_col(const Mat<eT>& in_m, const uword in_col)
    : subview<eT>(in_m, 0, in_col, in_m.n_rows, 1), colmem(subview<eT>::colptr(0)) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_col<eT>::subview_col(const Mat<eT>& in_m, const uword in_col,
                                    const uword in_row1, const uword in_n_rows)
    : subview<eT>(in_m, in_row1, in_col, in_n_rows, 1), colmem(subview<eT>::colptr(0)) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_col<eT>::subview_col(const subview_col<eT>& in)
    : subview<eT>(in)  // interprets 'subview_col' as 'subview'
      ,
      colmem(in.colmem) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_col<eT>::subview_col(subview_col<eT>&& in)
    : subview<eT>(std::move(in))  // interprets 'subview_col' as 'subview'
      ,
      colmem(in.colmem) {
  arma_extra_debug_sigprint();

  access::rw(in.colmem) = nullptr;
}

template <typename eT>
inline void subview_col<eT>::operator=(const subview<eT>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);
}

template <typename eT>
inline void subview_col<eT>::operator=(const subview_col<eT>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);  // interprets 'subview_col' as 'subview'
}

template <typename eT>
inline void subview_col<eT>::operator=(const std::initializer_list<eT>& list) {
  arma_extra_debug_sigprint();

  const uword N = uword(list.size());

  arma_debug_assert_same_size(subview<eT>::n_rows, subview<eT>::n_cols, N, 1,
                              "copy into submatrix");

  arrayops::copy(access::rwp(colmem), list.begin(), N);
}

template <typename eT>
inline void subview_col<eT>::operator=(const eT val) {
  arma_extra_debug_sigprint();

  if (subview<eT>::n_elem != 1) {
    arma_debug_assert_same_size(subview<eT>::n_rows, subview<eT>::n_cols, 1, 1,
                                "copy into submatrix");
  }

  access::rw(colmem[0]) = val;
}

template <typename eT>
template <typename T1>
inline void subview_col<eT>::operator=(const Base<eT, T1>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);
}

template <typename eT>
template <typename T1>
inline void subview_col<eT>::operator=(const SpBase<eT, T1>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X.get_ref());
}

template <typename eT>
template <typename T1, typename gen_type>
inline typename enable_if2<is_same_type<typename T1::elem_type, eT>::value, void>::result
subview_col<eT>::operator=(const Gen<T1, gen_type>& in) {
  arma_extra_debug_sigprint();

  arma_debug_assert_same_size(subview<eT>::n_rows, uword(1), in.n_rows,
                              (in.is_col ? uword(1) : in.n_cols), "copy into submatrix");

  in.apply(*this);
}

template <typename eT>
arma_inline const Op<subview_col<eT>, op_htrans> subview_col<eT>::t() const {
  return Op<subview_col<eT>, op_htrans>(*this);
}

template <typename eT>
arma_inline const Op<subview_col<eT>, op_htrans> subview_col<eT>::ht() const {
  return Op<subview_col<eT>, op_htrans>(*this);
}

template <typename eT>
arma_inline const Op<subview_col<eT>, op_strans> subview_col<eT>::st() const {
  return Op<subview_col<eT>, op_strans>(*this);
}

template <typename eT>
arma_inline const Op<subview_col<eT>, op_strans> subview_col<eT>::as_row() const {
  return Op<subview_col<eT>, op_strans>(*this);
}

template <typename eT>
inline void subview_col<eT>::fill(const eT val) {
  arma_extra_debug_sigprint();

  arrayops::inplace_set(access::rwp(colmem), val, subview<eT>::n_rows);
}

template <typename eT>
inline void subview_col<eT>::zeros() {
  arma_extra_debug_sigprint();

  arrayops::fill_zeros(access::rwp(colmem), subview<eT>::n_rows);
}

template <typename eT>
inline void subview_col<eT>::ones() {
  arma_extra_debug_sigprint();

  arrayops::inplace_set(access::rwp(colmem), eT(1), subview<eT>::n_rows);
}

template <typename eT>
arma_inline eT subview_col<eT>::at_alt(const uword ii) const {
  const eT* colmem_aligned = colmem;
  memory::mark_as_aligned(colmem_aligned);

  return colmem_aligned[ii];
}

template <typename eT>
arma_inline eT& subview_col<eT>::operator[](const uword ii) {
  return access::rw(colmem[ii]);
}

template <typename eT>
arma_inline eT subview_col<eT>::operator[](const uword ii) const {
  return colmem[ii];
}

template <typename eT>
inline eT& subview_col<eT>::operator()(const uword ii) {
  arma_debug_check_bounds((ii >= subview<eT>::n_elem),
                          "subview::operator(): index out of bounds");

  return access::rw(colmem[ii]);
}

template <typename eT>
inline eT subview_col<eT>::operator()(const uword ii) const {
  arma_debug_check_bounds((ii >= subview<eT>::n_elem),
                          "subview::operator(): index out of bounds");

  return colmem[ii];
}

template <typename eT>
inline eT& subview_col<eT>::operator()(const uword in_row, const uword in_col) {
  arma_debug_check_bounds(((in_row >= subview<eT>::n_rows) || (in_col > 0)),
                          "subview::operator(): index out of bounds");

  return access::rw(colmem[in_row]);
}

template <typename eT>
inline eT subview_col<eT>::operator()(const uword in_row, const uword in_col) const {
  arma_debug_check_bounds(((in_row >= subview<eT>::n_rows) || (in_col > 0)),
                          "subview::operator(): index out of bounds");

  return colmem[in_row];
}

template <typename eT>
inline eT& subview_col<eT>::at(const uword in_row, const uword) {
  return access::rw(colmem[in_row]);
}

template <typename eT>
inline eT subview_col<eT>::at(const uword in_row, const uword) const {
  return colmem[in_row];
}

template <typename eT>
arma_inline eT* subview_col<eT>::colptr(const uword) {
  return const_cast<eT*>(colmem);
}

template <typename eT>
arma_inline const eT* subview_col<eT>::colptr(const uword) const {
  return colmem;
}

template <typename eT>
inline subview_col<eT> subview_col<eT>::rows(const uword in_row1, const uword in_row2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_row1 > in_row2) || (in_row2 >= subview<eT>::n_rows)),
      "subview_col::rows(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;

  const uword base_row1 = this->aux_row1 + in_row1;

  return subview_col<eT>(this->m, this->aux_col1, base_row1, subview_n_rows);
}

template <typename eT>
inline const subview_col<eT> subview_col<eT>::rows(const uword in_row1,
                                                   const uword in_row2) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_row1 > in_row2) || (in_row2 >= subview<eT>::n_rows)),
      "subview_col::rows(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;

  const uword base_row1 = this->aux_row1 + in_row1;

  return subview_col<eT>(this->m, this->aux_col1, base_row1, subview_n_rows);
}

template <typename eT>
inline subview_col<eT> subview_col<eT>::subvec(const uword in_row1, const uword in_row2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_row1 > in_row2) || (in_row2 >= subview<eT>::n_rows)),
      "subview_col::subvec(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;

  const uword base_row1 = this->aux_row1 + in_row1;

  return subview_col<eT>(this->m, this->aux_col1, base_row1, subview_n_rows);
}

template <typename eT>
inline const subview_col<eT> subview_col<eT>::subvec(const uword in_row1,
                                                     const uword in_row2) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_row1 > in_row2) || (in_row2 >= subview<eT>::n_rows)),
      "subview_col::subvec(): indices out of bounds or incorrectly used");

  const uword subview_n_rows = in_row2 - in_row1 + 1;

  const uword base_row1 = this->aux_row1 + in_row1;

  return subview_col<eT>(this->m, this->aux_col1, base_row1, subview_n_rows);
}

template <typename eT>
inline subview_col<eT> subview_col<eT>::subvec(const uword start_row, const SizeMat& s) {
  arma_extra_debug_sigprint();

  arma_debug_check((s.n_cols != 1),
                   "subview_col::subvec(): given size does not specify a column vector");

  arma_debug_check_bounds(((start_row >= subview<eT>::n_rows) ||
                           ((start_row + s.n_rows) > subview<eT>::n_rows)),
                          "subview_col::subvec(): size out of bounds");

  const uword base_row1 = this->aux_row1 + start_row;

  return subview_col<eT>(this->m, this->aux_col1, base_row1, s.n_rows);
}

template <typename eT>
inline const subview_col<eT> subview_col<eT>::subvec(const uword start_row,
                                                     const SizeMat& s) const {
  arma_extra_debug_sigprint();

  arma_debug_check((s.n_cols != 1),
                   "subview_col::subvec(): given size does not specify a column vector");

  arma_debug_check_bounds(((start_row >= subview<eT>::n_rows) ||
                           ((start_row + s.n_rows) > subview<eT>::n_rows)),
                          "subview_col::subvec(): size out of bounds");

  const uword base_row1 = this->aux_row1 + start_row;

  return subview_col<eT>(this->m, this->aux_col1, base_row1, s.n_rows);
}

template <typename eT>
inline subview_col<eT> subview_col<eT>::head(const uword N) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_rows),
                          "subview_col::head(): size out of bounds");

  return subview_col<eT>(this->m, this->aux_col1, this->aux_row1, N);
}

template <typename eT>
inline const subview_col<eT> subview_col<eT>::head(const uword N) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_rows),
                          "subview_col::head(): size out of bounds");

  return subview_col<eT>(this->m, this->aux_col1, this->aux_row1, N);
}

template <typename eT>
inline subview_col<eT> subview_col<eT>::tail(const uword N) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_rows),
                          "subview_col::tail(): size out of bounds");

  const uword start_row = subview<eT>::aux_row1 + subview<eT>::n_rows - N;

  return subview_col<eT>(this->m, this->aux_col1, start_row, N);
}

template <typename eT>
inline const subview_col<eT> subview_col<eT>::tail(const uword N) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_rows),
                          "subview_col::tail(): size out of bounds");

  const uword start_row = subview<eT>::aux_row1 + subview<eT>::n_rows - N;

  return subview_col<eT>(this->m, this->aux_col1, start_row, N);
}

template <typename eT>
inline eT subview_col<eT>::min() const {
  arma_extra_debug_sigprint();

  if (subview<eT>::n_elem == 0) {
    arma_debug_check(true, "min(): object has no elements");

    return Datum<eT>::nan;
  }

  return op_min::direct_min(colmem, subview<eT>::n_elem);
}

template <typename eT>
inline eT subview_col<eT>::max() const {
  arma_extra_debug_sigprint();

  if (subview<eT>::n_elem == 0) {
    arma_debug_check(true, "max(): object has no elements");

    return Datum<eT>::nan;
  }

  return op_max::direct_max(colmem, subview<eT>::n_elem);
}

template <typename eT>
inline eT subview_col<eT>::min(uword& index_of_min_val) const {
  arma_extra_debug_sigprint();

  if (subview<eT>::n_elem == 0) {
    arma_debug_check(true, "min(): object has no elements");

    index_of_min_val = uword(0);

    return Datum<eT>::nan;
  } else {
    return op_min::direct_min(colmem, subview<eT>::n_elem, index_of_min_val);
  }
}

template <typename eT>
inline eT subview_col<eT>::max(uword& index_of_max_val) const {
  arma_extra_debug_sigprint();

  if (subview<eT>::n_elem == 0) {
    arma_debug_check(true, "max(): object has no elements");

    index_of_max_val = uword(0);

    return Datum<eT>::nan;
  } else {
    return op_max::direct_max(colmem, subview<eT>::n_elem, index_of_max_val);
  }
}

template <typename eT>
inline uword subview_col<eT>::index_min() const {
  arma_extra_debug_sigprint();

  uword index = 0;

  if (subview<eT>::n_elem == 0) {
    arma_debug_check(true, "index_min(): object has no elements");
  } else {
    op_min::direct_min(colmem, subview<eT>::n_elem, index);
  }

  return index;
}

template <typename eT>
inline uword subview_col<eT>::index_max() const {
  arma_extra_debug_sigprint();

  uword index = 0;

  if (subview<eT>::n_elem == 0) {
    arma_debug_check(true, "index_max(): object has no elements");
  } else {
    op_max::direct_max(colmem, subview<eT>::n_elem, index);
  }

  return index;
}

//
//
//

template <typename eT>
inline subview_cols<eT>::subview_cols(const Mat<eT>& in_m, const uword in_col1,
                                      const uword in_n_cols)
    : subview<eT>(in_m, 0, in_col1, in_m.n_rows, in_n_cols) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_cols<eT>::subview_cols(const subview_cols<eT>& in)
    : subview<eT>(in)  // interprets 'subview_cols' as 'subview'
{
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_cols<eT>::subview_cols(subview_cols<eT>&& in)
    : subview<eT>(std::move(in))  // interprets 'subview_cols' as 'subview'
{
  arma_extra_debug_sigprint();
}

template <typename eT>
inline void subview_cols<eT>::operator=(const subview<eT>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);
}

template <typename eT>
inline void subview_cols<eT>::operator=(const subview_cols<eT>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);  // interprets 'subview_cols' as 'subview'
}

template <typename eT>
inline void subview_cols<eT>::operator=(const std::initializer_list<eT>& list) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(list);
}

template <typename eT>
inline void subview_cols<eT>::operator=(
    const std::initializer_list<std::initializer_list<eT> >& list) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(list);
}

template <typename eT>
inline void subview_cols<eT>::operator=(const eT val) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(val);
}

template <typename eT>
template <typename T1>
inline void subview_cols<eT>::operator=(const Base<eT, T1>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X.get_ref());
}

template <typename eT>
template <typename T1>
inline void subview_cols<eT>::operator=(const SpBase<eT, T1>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X.get_ref());
}

template <typename eT>
template <typename T1, typename gen_type>
inline typename enable_if2<is_same_type<typename T1::elem_type, eT>::value, void>::result
subview_cols<eT>::operator=(const Gen<T1, gen_type>& in) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(in);
}

template <typename eT>
arma_inline const Op<subview_cols<eT>, op_htrans> subview_cols<eT>::t() const {
  return Op<subview_cols<eT>, op_htrans>(*this);
}

template <typename eT>
arma_inline const Op<subview_cols<eT>, op_htrans> subview_cols<eT>::ht() const {
  return Op<subview_cols<eT>, op_htrans>(*this);
}

template <typename eT>
arma_inline const Op<subview_cols<eT>, op_strans> subview_cols<eT>::st() const {
  return Op<subview_cols<eT>, op_strans>(*this);
}

template <typename eT>
arma_inline const Op<subview_cols<eT>, op_vectorise_col> subview_cols<eT>::as_col()
    const {
  return Op<subview_cols<eT>, op_vectorise_col>(*this);
}

template <typename eT>
inline eT subview_cols<eT>::at_alt(const uword ii) const {
  return operator[](ii);
}

template <typename eT>
inline eT& subview_cols<eT>::operator[](const uword ii) {
  const uword index = subview<eT>::aux_col1 * subview<eT>::m.n_rows + ii;

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_cols<eT>::operator[](const uword ii) const {
  const uword index = subview<eT>::aux_col1 * subview<eT>::m.n_rows + ii;

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline eT& subview_cols<eT>::operator()(const uword ii) {
  arma_debug_check_bounds((ii >= subview<eT>::n_elem),
                          "subview::operator(): index out of bounds");

  const uword index = subview<eT>::aux_col1 * subview<eT>::m.n_rows + ii;

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_cols<eT>::operator()(const uword ii) const {
  arma_debug_check_bounds((ii >= subview<eT>::n_elem),
                          "subview::operator(): index out of bounds");

  const uword index = subview<eT>::aux_col1 * subview<eT>::m.n_rows + ii;

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline eT& subview_cols<eT>::operator()(const uword in_row, const uword in_col) {
  arma_debug_check_bounds(
      ((in_row >= subview<eT>::n_rows) || (in_col >= subview<eT>::n_cols)),
      "subview::operator(): index out of bounds");

  const uword index = (in_col + subview<eT>::aux_col1) * subview<eT>::m.n_rows + in_row;

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_cols<eT>::operator()(const uword in_row, const uword in_col) const {
  arma_debug_check_bounds(
      ((in_row >= subview<eT>::n_rows) || (in_col >= subview<eT>::n_cols)),
      "subview::operator(): index out of bounds");

  const uword index = (in_col + subview<eT>::aux_col1) * subview<eT>::m.n_rows + in_row;

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline eT& subview_cols<eT>::at(const uword in_row, const uword in_col) {
  const uword index = (in_col + subview<eT>::aux_col1) * subview<eT>::m.n_rows + in_row;

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_cols<eT>::at(const uword in_row, const uword in_col) const {
  const uword index = (in_col + subview<eT>::aux_col1) * subview<eT>::m.n_rows + in_row;

  return subview<eT>::m.mem[index];
}

template <typename eT>
arma_inline eT* subview_cols<eT>::colptr(const uword in_col) {
  return &access::rw((const_cast<Mat<eT>&>(subview<eT>::m))
                         .mem[(in_col + subview<eT>::aux_col1) * subview<eT>::m.n_rows]);
}

template <typename eT>
arma_inline const eT* subview_cols<eT>::colptr(const uword in_col) const {
  return &subview<eT>::m.mem[(in_col + subview<eT>::aux_col1) * subview<eT>::m.n_rows];
}

//
//
//

template <typename eT>
inline subview_row<eT>::subview_row(const Mat<eT>& in_m, const uword in_row)
    : subview<eT>(in_m, in_row, 0, 1, in_m.n_cols) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_row<eT>::subview_row(const Mat<eT>& in_m, const uword in_row,
                                    const uword in_col1, const uword in_n_cols)
    : subview<eT>(in_m, in_row, in_col1, 1, in_n_cols) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_row<eT>::subview_row(const subview_row<eT>& in)
    : subview<eT>(in)  // interprets 'subview_row' as 'subview'
{
  arma_extra_debug_sigprint();
}

template <typename eT>
inline subview_row<eT>::subview_row(subview_row<eT>&& in)
    : subview<eT>(std::move(in))  // interprets 'subview_row' as 'subview'
{
  arma_extra_debug_sigprint();
}

template <typename eT>
inline void subview_row<eT>::operator=(const subview<eT>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);
}

template <typename eT>
inline void subview_row<eT>::operator=(const subview_row<eT>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);  // interprets 'subview_row' as 'subview'
}

template <typename eT>
inline void subview_row<eT>::operator=(const eT val) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(val);  // interprets 'subview_row' as 'subview'
}

template <typename eT>
inline void subview_row<eT>::operator=(const std::initializer_list<eT>& list) {
  arma_extra_debug_sigprint();

  const uword N = uword(list.size());

  arma_debug_assert_same_size(subview<eT>::n_rows, subview<eT>::n_cols, 1, N,
                              "copy into submatrix");

  auto it = list.begin();

  for (uword ii = 0; ii < N; ++ii) {
    (*this).operator[](ii) = (*it);
    ++it;
  }
}

template <typename eT>
template <typename T1>
inline void subview_row<eT>::operator=(const Base<eT, T1>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X);
}

template <typename eT>
template <typename T1>
inline void subview_row<eT>::operator=(const SpBase<eT, T1>& X) {
  arma_extra_debug_sigprint();

  subview<eT>::operator=(X.get_ref());
}

template <typename eT>
template <typename T1, typename gen_type>
inline typename enable_if2<is_same_type<typename T1::elem_type, eT>::value, void>::result
subview_row<eT>::operator=(const Gen<T1, gen_type>& in) {
  arma_extra_debug_sigprint();

  arma_debug_assert_same_size(uword(1), subview<eT>::n_cols,
                              (in.is_row ? uword(1) : in.n_rows), in.n_cols,
                              "copy into submatrix");

  in.apply(*this);
}

template <typename eT>
arma_inline const Op<subview_row<eT>, op_htrans> subview_row<eT>::t() const {
  return Op<subview_row<eT>, op_htrans>(*this);
}

template <typename eT>
arma_inline const Op<subview_row<eT>, op_htrans> subview_row<eT>::ht() const {
  return Op<subview_row<eT>, op_htrans>(*this);
}

template <typename eT>
arma_inline const Op<subview_row<eT>, op_strans> subview_row<eT>::st() const {
  return Op<subview_row<eT>, op_strans>(*this);
}

template <typename eT>
arma_inline const Op<subview_row<eT>, op_strans> subview_row<eT>::as_col() const {
  return Op<subview_row<eT>, op_strans>(*this);
}

template <typename eT>
inline eT subview_row<eT>::at_alt(const uword ii) const {
  const uword index =
      (ii + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows + (subview<eT>::aux_row1);

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline eT& subview_row<eT>::operator[](const uword ii) {
  const uword index =
      (ii + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows + (subview<eT>::aux_row1);

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_row<eT>::operator[](const uword ii) const {
  const uword index =
      (ii + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows + (subview<eT>::aux_row1);

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline eT& subview_row<eT>::operator()(const uword ii) {
  arma_debug_check_bounds((ii >= subview<eT>::n_elem),
                          "subview::operator(): index out of bounds");

  const uword index =
      (ii + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows + (subview<eT>::aux_row1);

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_row<eT>::operator()(const uword ii) const {
  arma_debug_check_bounds((ii >= subview<eT>::n_elem),
                          "subview::operator(): index out of bounds");

  const uword index =
      (ii + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows + (subview<eT>::aux_row1);

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline eT& subview_row<eT>::operator()(const uword in_row, const uword in_col) {
  arma_debug_check_bounds(((in_row > 0) || (in_col >= subview<eT>::n_cols)),
                          "subview::operator(): index out of bounds");

  const uword index = (in_col + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows +
                      (subview<eT>::aux_row1);

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_row<eT>::operator()(const uword in_row, const uword in_col) const {
  arma_debug_check_bounds(((in_row > 0) || (in_col >= subview<eT>::n_cols)),
                          "subview::operator(): index out of bounds");

  const uword index = (in_col + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows +
                      (subview<eT>::aux_row1);

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline eT& subview_row<eT>::at(const uword, const uword in_col) {
  const uword index = (in_col + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows +
                      (subview<eT>::aux_row1);

  return access::rw((const_cast<Mat<eT>&>(subview<eT>::m)).mem[index]);
}

template <typename eT>
inline eT subview_row<eT>::at(const uword, const uword in_col) const {
  const uword index = (in_col + (subview<eT>::aux_col1)) * (subview<eT>::m).n_rows +
                      (subview<eT>::aux_row1);

  return subview<eT>::m.mem[index];
}

template <typename eT>
inline subview_row<eT> subview_row<eT>::cols(const uword in_col1, const uword in_col2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_col1 > in_col2) || (in_col2 >= subview<eT>::n_cols)),
      "subview_row::cols(): indices out of bounds or incorrectly used");

  const uword subview_n_cols = in_col2 - in_col1 + 1;

  const uword base_col1 = this->aux_col1 + in_col1;

  return subview_row<eT>(this->m, this->aux_row1, base_col1, subview_n_cols);
}

template <typename eT>
inline const subview_row<eT> subview_row<eT>::cols(const uword in_col1,
                                                   const uword in_col2) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_col1 > in_col2) || (in_col2 >= subview<eT>::n_cols)),
      "subview_row::cols(): indices out of bounds or incorrectly used");

  const uword subview_n_cols = in_col2 - in_col1 + 1;

  const uword base_col1 = this->aux_col1 + in_col1;

  return subview_row<eT>(this->m, this->aux_row1, base_col1, subview_n_cols);
}

template <typename eT>
inline subview_row<eT> subview_row<eT>::subvec(const uword in_col1, const uword in_col2) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_col1 > in_col2) || (in_col2 >= subview<eT>::n_cols)),
      "subview_row::subvec(): indices out of bounds or incorrectly used");

  const uword subview_n_cols = in_col2 - in_col1 + 1;

  const uword base_col1 = this->aux_col1 + in_col1;

  return subview_row<eT>(this->m, this->aux_row1, base_col1, subview_n_cols);
}

template <typename eT>
inline const subview_row<eT> subview_row<eT>::subvec(const uword in_col1,
                                                     const uword in_col2) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds(
      ((in_col1 > in_col2) || (in_col2 >= subview<eT>::n_cols)),
      "subview_row::subvec(): indices out of bounds or incorrectly used");

  const uword subview_n_cols = in_col2 - in_col1 + 1;

  const uword base_col1 = this->aux_col1 + in_col1;

  return subview_row<eT>(this->m, this->aux_row1, base_col1, subview_n_cols);
}

template <typename eT>
inline subview_row<eT> subview_row<eT>::subvec(const uword start_col, const SizeMat& s) {
  arma_extra_debug_sigprint();

  arma_debug_check((s.n_rows != 1),
                   "subview_row::subvec(): given size does not specify a row vector");

  arma_debug_check_bounds(((start_col >= subview<eT>::n_cols) ||
                           ((start_col + s.n_cols) > subview<eT>::n_cols)),
                          "subview_row::subvec(): size out of bounds");

  const uword base_col1 = this->aux_col1 + start_col;

  return subview_row<eT>(this->m, this->aux_row1, base_col1, s.n_cols);
}

template <typename eT>
inline const subview_row<eT> subview_row<eT>::subvec(const uword start_col,
                                                     const SizeMat& s) const {
  arma_extra_debug_sigprint();

  arma_debug_check((s.n_rows != 1),
                   "subview_row::subvec(): given size does not specify a row vector");

  arma_debug_check_bounds(((start_col >= subview<eT>::n_cols) ||
                           ((start_col + s.n_cols) > subview<eT>::n_cols)),
                          "subview_row::subvec(): size out of bounds");

  const uword base_col1 = this->aux_col1 + start_col;

  return subview_row<eT>(this->m, this->aux_row1, base_col1, s.n_cols);
}

template <typename eT>
inline subview_row<eT> subview_row<eT>::head(const uword N) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_cols),
                          "subview_row::head(): size out of bounds");

  return subview_row<eT>(this->m, this->aux_row1, this->aux_col1, N);
}

template <typename eT>
inline const subview_row<eT> subview_row<eT>::head(const uword N) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_cols),
                          "subview_row::head(): size out of bounds");

  return subview_row<eT>(this->m, this->aux_row1, this->aux_col1, N);
}

template <typename eT>
inline subview_row<eT> subview_row<eT>::tail(const uword N) {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_cols),
                          "subview_row::tail(): size out of bounds");

  const uword start_col = subview<eT>::aux_col1 + subview<eT>::n_cols - N;

  return subview_row<eT>(this->m, this->aux_row1, start_col, N);
}

template <typename eT>
inline const subview_row<eT> subview_row<eT>::tail(const uword N) const {
  arma_extra_debug_sigprint();

  arma_debug_check_bounds((N > subview<eT>::n_cols),
                          "subview_row::tail(): size out of bounds");

  const uword start_col = subview<eT>::aux_col1 + subview<eT>::n_cols - N;

  return subview_row<eT>(this->m, this->aux_row1, start_col, N);
}

template <typename eT>
inline uword subview_row<eT>::index_min() const {
  const Proxy<subview_row<eT> > P(*this);

  uword index = 0;

  if (P.get_n_elem() == 0) {
    arma_debug_check(true, "index_min(): object has no elements");
  } else {
    op_min::min_with_index(P, index);
  }

  return index;
}

template <typename eT>
inline uword subview_row<eT>::index_max() const {
  const Proxy<subview_row<eT> > P(*this);

  uword index = 0;

  if (P.get_n_elem() == 0) {
    arma_debug_check(true, "index_max(): object has no elements");
  } else {
    op_max::max_with_index(P, index);
  }

  return index;
}

template <typename eT>
inline typename subview<eT>::row_iterator subview_row<eT>::begin() {
  return typename subview<eT>::row_iterator(*this, subview<eT>::aux_row1,
                                            subview<eT>::aux_col1);
}

template <typename eT>
inline typename subview<eT>::const_row_iterator subview_row<eT>::begin() const {
  return typename subview<eT>::const_row_iterator(*this, subview<eT>::aux_row1,
                                                  subview<eT>::aux_col1);
}

template <typename eT>
inline typename subview<eT>::const_row_iterator subview_row<eT>::cbegin() const {
  return typename subview<eT>::const_row_iterator(*this, subview<eT>::aux_row1,
                                                  subview<eT>::aux_col1);
}

template <typename eT>
inline typename subview<eT>::row_iterator subview_row<eT>::end() {
  return typename subview<eT>::row_iterator(
      *this, subview<eT>::aux_row1 + subview<eT>::n_rows, subview<eT>::aux_col1);
}

template <typename eT>
inline typename subview<eT>::const_row_iterator subview_row<eT>::end() const {
  return typename subview<eT>::const_row_iterator(
      *this, subview<eT>::aux_row1 + subview<eT>::n_rows, subview<eT>::aux_col1);
}

template <typename eT>
inline typename subview<eT>::const_row_iterator subview_row<eT>::cend() const {
  return typename subview<eT>::const_row_iterator(
      *this, subview<eT>::aux_row1 + subview<eT>::n_rows, subview<eT>::aux_col1);
}

//
//
//

template <typename eT>
inline subview_row_strans<eT>::subview_row_strans(const subview_row<eT>& in_sv_row)
    : sv_row(in_sv_row), n_rows(in_sv_row.n_cols), n_elem(in_sv_row.n_elem) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline void subview_row_strans<eT>::extract(Mat<eT>& out) const {
  arma_extra_debug_sigprint();

  // NOTE: this function assumes that matrix 'out' has already been set to the correct
  // size

  const Mat<eT>& X = sv_row.m;

  eT* out_mem = out.memptr();

  const uword row = sv_row.aux_row1;
  const uword start_col = sv_row.aux_col1;
  const uword sv_row_n_cols = sv_row.n_cols;

  uword ii, jj;

  for (ii = 0, jj = 1; jj < sv_row_n_cols; ii += 2, jj += 2) {
    const eT tmp1 = X.at(row, start_col + ii);
    const eT tmp2 = X.at(row, start_col + jj);

    out_mem[ii] = tmp1;
    out_mem[jj] = tmp2;
  }

  if (ii < sv_row_n_cols) {
    out_mem[ii] = X.at(row, start_col + ii);
  }
}

template <typename eT>
inline eT subview_row_strans<eT>::at_alt(const uword ii) const {
  return sv_row[ii];
}

template <typename eT>
inline eT subview_row_strans<eT>::operator[](const uword ii) const {
  return sv_row[ii];
}

template <typename eT>
inline eT subview_row_strans<eT>::operator()(const uword ii) const {
  return sv_row(ii);
}

template <typename eT>
inline eT subview_row_strans<eT>::operator()(const uword in_row,
                                             const uword in_col) const {
  return sv_row(in_col, in_row);  // deliberately swapped
}

template <typename eT>
inline eT subview_row_strans<eT>::at(const uword in_row, const uword) const {
  return sv_row.at(0, in_row);  // deliberately swapped
}

//
//
//

template <typename eT>
inline subview_row_htrans<eT>::subview_row_htrans(const subview_row<eT>& in_sv_row)
    : sv_row(in_sv_row), n_rows(in_sv_row.n_cols), n_elem(in_sv_row.n_elem) {
  arma_extra_debug_sigprint();
}

template <typename eT>
inline void subview_row_htrans<eT>::extract(Mat<eT>& out) const {
  arma_extra_debug_sigprint();

  // NOTE: this function assumes that matrix 'out' has already been set to the correct
  // size

  const Mat<eT>& X = sv_row.m;

  eT* out_mem = out.memptr();

  const uword row = sv_row.aux_row1;
  const uword start_col = sv_row.aux_col1;
  const uword sv_row_n_cols = sv_row.n_cols;

  for (uword ii = 0; ii < sv_row_n_cols; ++ii) {
    out_mem[ii] = access::alt_conj(X.at(row, start_col + ii));
  }
}

template <typename eT>
inline eT subview_row_htrans<eT>::at_alt(const uword ii) const {
  return access::alt_conj(sv_row[ii]);
}

template <typename eT>
inline eT subview_row_htrans<eT>::operator[](const uword ii) const {
  return access::alt_conj(sv_row[ii]);
}

template <typename eT>
inline eT subview_row_htrans<eT>::operator()(const uword ii) const {
  return access::alt_conj(sv_row(ii));
}

template <typename eT>
inline eT subview_row_htrans<eT>::operator()(const uword in_row,
                                             const uword in_col) const {
  return access::alt_conj(sv_row(in_col, in_row));  // deliberately swapped
}

template <typename eT>
inline eT subview_row_htrans<eT>::at(const uword in_row, const uword) const {
  return access::alt_conj(sv_row.at(0, in_row));  // deliberately swapped
}

//! @}
