diff --git a/c++/cppdlr/utils.hpp b/c++/cppdlr/utils.hpp
index 4a8b109..dfef7c6 100644
--- a/c++/cppdlr/utils.hpp
+++ b/c++/cppdlr/utils.hpp
@@ -19,9 +19,15 @@
 #include <nda/nda.hpp>
 #include <nda/blas.hpp>
 
-
 namespace cppdlr {
-  using dcomplex = std::complex<double>;
+
+  /**
+   * Calculate the squared norm of a vector
+   *
+   * @param v The input vector
+   * @return x The squared norm of the vector
+   */
+  double normsq(nda::MemoryVector auto const &v) { return nda::real(nda::blas::dotc(v, v)); }
 
   /** 
    * Class constructor for barycheb: barycentric Lagrange interpolation at
@@ -116,10 +122,10 @@ namespace cppdlr {
     // Compute norms of rows of input matrix, and rescale eps tolerance
     auto norms   = nda::vector<double>(m);
     double epssq = eps * eps;
-    for (int j = 0; j < m; ++j) { norms(j) = nda::real(nda::blas::dotc(aa(j, _), aa(j, _))); }
+    for (int j = 0; j < m; ++j) { norms(j) = normsq(aa(j, _)); }
 
     // Begin pivoted double Gram-Schmidt procedure
-    int jpiv = 0, jj = 0;
+    int jpiv   = 0;
     double nrm = 0;
     auto piv   = nda::arange(m);
     auto tmp   = nda::vector<S>(n);
@@ -137,38 +143,29 @@ namespace cppdlr {
       }
 
       // Swap current row with chosen pivot row
-      tmp         = aa(j, _);
-      aa(j, _)    = aa(jpiv, _);
-      aa(jpiv, _) = tmp;
-
-      nrm         = norms(j);
-      norms(j)    = norms(jpiv);
-      norms(jpiv) = nrm;
-
-      jj        = piv(j);
-      piv(j)    = piv(jpiv);
-      piv(jpiv) = jj;
+      deep_swap(aa(j, _), aa(jpiv, _));
+      std::swap(norms(j), norms(jpiv));
+      std::swap(piv(j), piv(jpiv));
 
       // Orthogonalize current rows (now the chosen pivot row) against all
       // previously chosen rows
       for (int k = 0; k < j; ++k) { aa(j, _) = aa(j, _) - aa(k, _) * nda::blas::dotc(aa(k, _), aa(j, _)); }
 
       // Get norm of current row
-      nrm = nda::real(nda::blas::dotc(aa(j, _), aa(j, _)));
-      //nrm = nda::norm(aa(j, _));
+      nrm = normsq(aa(j, _));
 
       // Terminate if sufficiently small, and return previously selected rows
       // (not including current row)
       if (nrm <= epssq) { return {aa(nda::range(0, j), _), norms(nda::range(0, j)), piv(nda::range(0, j))}; };
 
       // Normalize current row
-      aa(j, _) = aa(j, _) * (1 / sqrt(nrm));
+      aa(j, _) /= sqrt(nrm);
 
       // Orthogonalize remaining rows against current row
       for (int k = j + 1; k < m; ++k) {
         if (norms(k) <= epssq) { continue; } // Can skip rows with norm less than tolerance
         aa(k, _) = aa(k, _) - aa(j, _) * nda::blas::dotc(aa(j, _), aa(k, _));
-        norms(k) = nda::real(nda::blas::dotc(aa(k, _), aa(k, _)));
+        norms(k) = normsq(aa(k, _));
       }
     }
 
@@ -211,22 +208,21 @@ namespace cppdlr {
     if (m % 2 != 0) { throw std::runtime_error("Input matrix must have even number of rows."); }
 
     // Copy input data, re-ordering rows to make symmetric rows adjacent.
-    auto aa = typename T::regular_type(m, n);
+    auto aa                    = typename T::regular_type(m, n);
     aa(nda::range(0, m, 2), _) = a(nda::range(0, m / 2), _);
     aa(nda::range(1, m, 2), _) = a(nda::range(m - 1, m / 2 - 1, -1), _);
 
     // Compute norms of rows of input matrix, and rescale eps tolerance
     auto norms   = nda::vector<double>(m);
     double epssq = eps * eps;
-    for (int j = 0; j < m; ++j) { norms(j) = nda::real(nda::blas::dotc(aa(j, _), aa(j, _))); }
+    for (int j = 0; j < m; ++j) { norms(j) = normsq(aa(j, _)); }
 
     // Begin pivoted double Gram-Schmidt procedure
-    int jpiv = 0, jj = 0;
-    double nrm = 0;
-    auto piv   = nda::arange(0, m);
+    int jpiv                 = 0;
+    double nrm               = 0;
+    auto piv                 = nda::arange(0, m);
     piv(nda::range(0, m, 2)) = nda::arange(0, m / 2); // Re-order pivots to match re-ordered input matrix
     piv(nda::range(1, m, 2)) = nda::arange(m - 1, m / 2 - 1, -1);
-    auto tmp = nda::vector<S>(n);
 
     if (maxrnk % 2 != 0) { // If n < m and n is odd, decrease maxrnk to maintain symmetry
       maxrnk -= 1;
@@ -245,46 +241,32 @@ namespace cppdlr {
       }
 
       // Swap current row pair with chosen pivot row pair
-      tmp             = aa(j, _);
-      aa(j, _)        = aa(jpiv, _);
-      aa(jpiv, _)     = tmp;
-      tmp             = aa(j + 1, _);
-      aa(j + 1, _)    = aa(jpiv + 1, _);
-      aa(jpiv + 1, _) = tmp;
-
-      nrm             = norms(j);
-      norms(j)        = norms(jpiv);
-      norms(jpiv)     = nrm;
-      nrm             = norms(j + 1);
-      norms(j + 1)    = norms(jpiv + 1);
-      norms(jpiv + 1) = nrm;
-
-      jj            = piv(j);
-      piv(j)        = piv(jpiv);
-      piv(jpiv)     = jj;
-      jj            = piv(j + 1);
-      piv(j + 1)    = piv(jpiv + 1);
-      piv(jpiv + 1) = jj;
+      deep_swap(aa(j, _), aa(jpiv, _));
+      deep_swap(aa(j + 1, _), aa(jpiv + 1, _));
+      std::swap(norms(j), norms(jpiv));
+      std::swap(norms(j + 1), norms(jpiv + 1));
+      std::swap(piv(j), piv(jpiv));
+      std::swap(piv(j + 1), piv(jpiv + 1));
 
       // Orthogonalize current row (now the first chosen pivot row) against all
       // previously chosen rows
       for (int k = 0; k < j; ++k) { aa(j, _) = aa(j, _) - aa(k, _) * nda::blas::dotc(aa(k, _), aa(j, _)); }
 
       // Get norm of current row
-      nrm = nda::real(nda::blas::dotc(aa(j, _), aa(j, _)));
+      nrm = normsq(aa(j, _));
 
       // Terminate if sufficiently small, and return previously selected rows
       // (not including current row)
       if (nrm <= epssq) { return {aa(nda::range(0, j), _), norms(nda::range(0, j)), piv(nda::range(0, j))}; };
 
       // Normalize current row
-      aa(j, _) = aa(j, _) * (1 / sqrt(nrm));
+      aa(j, _) /= sqrt(nrm);
 
       // Orthogonalize remaining rows against current row
       for (int k = j + 1; k < m; ++k) {
         if (norms(k) <= epssq) { continue; } // Can skip rows with norm less than tolerance
         aa(k, _) = aa(k, _) - aa(j, _) * nda::blas::dotc(aa(j, _), aa(k, _));
-        norms(k) = nda::real(nda::blas::dotc(aa(k, _), aa(k, _)));
+        norms(k) = normsq(aa(k, _));
       }
 
       // Orthogonalize current row (now the second chosen pivot row) against all
@@ -292,14 +274,13 @@ namespace cppdlr {
       for (int k = 0; k < j + 1; ++k) { aa(j + 1, _) = aa(j + 1, _) - aa(k, _) * nda::blas::dotc(aa(k, _), aa(j + 1, _)); }
 
       // Normalize current row
-      nrm          = nda::real(nda::blas::dotc(aa(j + 1, _), aa(j + 1, _)));
-      aa(j + 1, _) = aa(j + 1, _) * (1 / sqrt(nrm));
+      aa(j + 1, _) /= sqrt(normsq(aa(j + 1, _)));
 
       // Orthogonalize remaining rows against current row
       for (int k = j + 2; k < m; ++k) {
         if (norms(k) <= epssq) { continue; } // Can skip rows with norm less than tolerance
         aa(k, _) = aa(k, _) - aa(j + 1, _) * nda::blas::dotc(aa(j + 1, _), aa(k, _));
-        norms(k) = nda::real(nda::blas::dotc(aa(k, _), aa(k, _)));
+        norms(k) = normsq(aa(k, _));
       }
     }
 
@@ -352,7 +333,7 @@ namespace cppdlr {
       aa(nda::range(0, m, 2), _) = a(nda::range(0, m / 2), _);
       aa(nda::range(1, m, 2), _) = a(nda::range(m - 1, m / 2 - 1, -1), _);
     } else {
-      aa(0, _) = a((m - 1) / 2, _);
+      aa(0, _)                   = a((m - 1) / 2, _);
       aa(nda::range(1, m, 2), _) = a(nda::range(0, (m - 1) / 2), _);
       aa(nda::range(2, m, 2), _) = a(nda::range(m - 1, (m - 1) / 2, -1), _);
       //aa(m - 1, _)                   = a((m - 1) / 2, _);
@@ -360,10 +341,10 @@ namespace cppdlr {
 
     // Compute norms of rows of input matrix
     auto norms = nda::vector<double>(m);
-    for (int j = 0; j < m; ++j) { norms(j) = nda::real(nda::blas::dotc(aa(j, _), aa(j, _))); }
+    for (int j = 0; j < m; ++j) { norms(j) = normsq(aa(j, _)); }
 
     // Begin pivoted double Gram-Schmidt procedure
-    int jpiv = 0, jj = 0;
+    int jpiv   = 0;
     double nrm = 0;
     auto piv   = nda::arange(0, m);
     if (m % 2 == 0) {
@@ -375,23 +356,17 @@ namespace cppdlr {
       piv(nda::range(2, m, 2)) = nda::arange(m - 1, (m - 1) / 2, -1);
       //piv(m - 1)                   = (m - 1) / 2;
     }
-    auto tmp = nda::vector<S>(n);
 
     // If m odd, first choose middle row (now last row) as first pivot
 
     if (m % 2 == 1) {
-      //int j = 0;     // Index of current row
-      //jpiv  = 0; // Index of pivot row
-
       // Normalize
-      nrm      = nda::real(nda::blas::dotc(aa(0, _), aa(0, _)));
-      aa(0, _) = aa(0, _) * (1 / sqrt(nrm));
-      //aa(0, _) /= sqrt(nda::real(nda::blas::dotc(aa(0, _), aa(0, _))));
+      aa(0, _) /= sqrt(normsq(aa(0, _)));
 
       // Orthogonalize remaining rows against current row
       for (int k = 1; k < m; ++k) {
         aa(k, _) = aa(k, _) - aa(0, _) * nda::blas::dotc(aa(0, _), aa(k, _));
-        norms(k) = nda::real(nda::blas::dotc(aa(k, _), aa(k, _)));
+        norms(k) = normsq(aa(k, _));
       }
     }
 
@@ -410,39 +385,24 @@ namespace cppdlr {
       }
 
       // Swap current row pair with chosen pivot row pair
-      tmp             = aa(j, _);
-      aa(j, _)        = aa(jpiv, _);
-      aa(jpiv, _)     = tmp;
-      tmp             = aa(j + 1, _);
-      aa(j + 1, _)    = aa(jpiv + 1, _);
-      aa(jpiv + 1, _) = tmp;
-
-      nrm             = norms(j);
-      norms(j)        = norms(jpiv);
-      norms(jpiv)     = nrm;
-      nrm             = norms(j + 1);
-      norms(j + 1)    = norms(jpiv + 1);
-      norms(jpiv + 1) = nrm;
-
-      jj            = piv(j);
-      piv(j)        = piv(jpiv);
-      piv(jpiv)     = jj;
-      jj            = piv(j + 1);
-      piv(j + 1)    = piv(jpiv + 1);
-      piv(jpiv + 1) = jj;
+      deep_swap(aa(j, _), aa(jpiv, _));
+      deep_swap(aa(j + 1, _), aa(jpiv + 1, _));
+      std::swap(norms(j), norms(jpiv));
+      std::swap(norms(j + 1), norms(jpiv + 1));
+      std::swap(piv(j), piv(jpiv));
+      std::swap(piv(j + 1), piv(jpiv + 1));
 
       // Orthogonalize current row (now the first chosen pivot row) against all
       // previously chosen rows
       for (int k = 0; k < j; ++k) { aa(j, _) = aa(j, _) - aa(k, _) * nda::blas::dotc(aa(k, _), aa(j, _)); }
 
       // Normalize current row
-      nrm      = nda::real(nda::blas::dotc(aa(j, _), aa(j, _)));
-      aa(j, _) = aa(j, _) * (1 / sqrt(nrm));
+      aa(j, _) /= sqrt(normsq(aa(j, _)));
 
       // Orthogonalize remaining rows against current row
       for (int k = j + 1; k < m; ++k) {
         aa(k, _) = aa(k, _) - aa(j, _) * nda::blas::dotc(aa(j, _), aa(k, _));
-        norms(k) = nda::real(nda::blas::dotc(aa(k, _), aa(k, _)));
+        norms(k) = normsq(aa(k, _));
       }
 
       // Orthogonalize current row (now the second chosen pivot row) against all
@@ -450,13 +410,12 @@ namespace cppdlr {
       for (int k = 0; k < j + 1; ++k) { aa(j + 1, _) = aa(j + 1, _) - aa(k, _) * nda::blas::dotc(aa(k, _), aa(j + 1, _)); }
 
       // Normalize current row
-      nrm          = nda::real(nda::blas::dotc(aa(j + 1, _), aa(j + 1, _)));
-      aa(j + 1, _) = aa(j + 1, _) * (1 / sqrt(nrm));
+      aa(j + 1, _) /= sqrt(normsq(aa(j + 1, _)));
 
       // Orthogonalize remaining rows against current row
       for (int k = j + 2; k < m; ++k) {
         aa(k, _) = aa(k, _) - aa(j + 1, _) * nda::blas::dotc(aa(j + 1, _), aa(k, _));
-        norms(k) = nda::real(nda::blas::dotc(aa(k, _), aa(k, _)));
+        norms(k) = normsq(aa(k, _));
       }
     }
 
@@ -551,7 +510,7 @@ namespace cppdlr {
   * @return Contraction of the inner dimensions of \p a and \p b
   */
   template <nda::MemoryArray Ta, nda::MemoryArray Tb, nda::Scalar Sa = nda::get_value_t<Ta>, nda::Scalar Sb = nda::get_value_t<Tb>,
-            nda::Scalar S = typename std::common_type<Sa, Sb>::type>
+            nda::Scalar S = std::common_type_t<Sa, Sb>>
   nda::array<S, Ta::rank + Tb::rank - 2> arraymult(Ta const &a, Tb const &b) {
 
     // Get ranks of input arrays