Browse Source

feat: add type deduction, and better handling for aggregate initialization

Sam Jaffe 1 year ago
parent
commit
57a4763a16

+ 1 - 1
.clang-format

@@ -75,7 +75,7 @@ KeepEmptyLinesAtTheStartOfBlocks: true
 MacroBlockBegin: ''
 MacroBlockEnd:   ''
 MaxEmptyLinesToKeep: 1
-NamespaceIndentation: All
+NamespaceIndentation: None
 ObjCBlockIndentWidth: 2
 ObjCSpaceAfterProperty: false
 ObjCSpaceBeforeProtocolList: true

+ 6 - 2
.gitmodules

@@ -1,6 +1,10 @@
 [submodule "extern/vector"]
-	path = extern/vector
+	path = external/vector
 	url = https://gogs.sjaffe.name/sjjaffe/cpp-mathmatic-vector
 [submodule "extern/expect"]
-	path = extern/expect
+	path = external/expect
 	url = https://gogs.sjaffe.name/sjjaffe/cpp-expect
+[submodule "external/test-helpers"]
+	path = external/test-helpers
+	url = ssh://git@gogs.sjaffe.name:3000/sjjaffe/cpp-test-helpers.git
+	branch = main

+ 0 - 1
extern/expect

@@ -1 +0,0 @@
-Subproject commit f096edddd9d8eb2ba07c2dd7ec385500c0576214

+ 0 - 1
extern/vector

@@ -1 +0,0 @@
-Subproject commit 8c9554860423c5cb000bdc233bd5eab85acfe4a5

+ 1 - 0
external/expect

@@ -0,0 +1 @@
+Subproject commit ff7cd322bd883b085d383253ceefb27933e6d6cf

+ 1 - 0
external/test-helpers

@@ -0,0 +1 @@
+Subproject commit 2860eea66cd30adf2550f47e710ef7ca443fda5a

+ 1 - 0
external/vector

@@ -0,0 +1 @@
+Subproject commit a918330ac9b4a9faf2e3da692474a92b8f193ed0

+ 17 - 17
include/math/matrix/forward.h

@@ -2,22 +2,22 @@
 
 #include <cstdlib>
 
-namespace math { namespace matrix {
-  template <typename T, std::size_t R, std::size_t C> class matrix;
-}}
+namespace math::matrix {
+template <typename T, std::size_t R, std::size_t C> class matrix;
+}
 
-namespace math { namespace vector {
-  template <typename T, std::size_t N> class vector;
-}}
+namespace math::vector {
+template <typename T, std::size_t N> class vector;
+}
 
-namespace math { namespace matrix { namespace concat_strategy {
-  struct {
-  } horizontal;
-  using horizontal_concat_t = decltype(horizontal);
-  struct {
-  } vertical;
-  using vertical_concat_t = decltype(vertical);
-  struct {
-  } diagonal;
-  using diagonal_concat_t = decltype(diagonal);
-}}}
+namespace math::matrix::concat_strategy {
+struct {
+} horizontal;
+using horizontal_concat_t = decltype(horizontal);
+struct {
+} vertical;
+using vertical_concat_t = decltype(vertical);
+struct {
+} diagonal;
+using diagonal_concat_t = decltype(diagonal);
+}

+ 26 - 0
include/math/matrix/macro.h

@@ -0,0 +1,26 @@
+//
+//  macro.h
+//  matrix
+//
+//  Created by Sam Jaffe on 2/17/24.
+//  Copyright © 2024 Sam Jaffe. All rights reserved.
+//
+
+#include <math/vector/macro.h>
+
+#define MATRIX_DISABLE_IF_MATRIX(_type, t, r, c)                               \
+  std::enable_if_t<!is_matrix_v<_type>, matrix<t, r, c>>
+
+#define MATRIX_FOR_EACH_RANGE(i, i_max, j, j_max)                              \
+  for (size_t i = 0; i < i_max; ++i)                                           \
+    for (size_t j = 0; j < j_max; ++j)
+#define MATRIX_FOR_EACH(i, j) MATRIX_FOR_EACH_RANGE(i, R, j, C)
+
+#define MATRIX_CTOR_N_ARGS(N_IN)                                               \
+  DEFER_RESOLUTION matrix(REPEAT(N_IN, ARGN, COMMA, vector::vector<T, C>),     \
+                          DEFERRED_ENABLE_IF_T(N_IN == R, bool) = true)        \
+      : matrix(std::array{REPEAT(N_IN, ARGN, COMMA)})
+
+#define MATRIX_CTOR_DEDUCTION(N_IN)                                            \
+  template <typename T, size_t N>                                              \
+  matrix(REPEAT(N_IN, ARGN, COMMA, vector::vector<T, N>))->matrix<T, N_IN, N>

+ 177 - 178
include/math/matrix/matrix.hpp

@@ -14,183 +14,182 @@
 #include "math/matrix/row_reference.hpp"
 #include "math/matrix/traits.hpp"
 
-#define MATRIX_DISABLE_IF_MATRIX(_type, t, r, c)                               \
-  typename std::enable_if<!is_matrix<_type>::value, matrix<t, r, c>>::type
-
-#define MATRIX_FOR_EACH_RANGE(i, i_max, j, j_max)                              \
-  for (size_t i = 0; i < i_max; ++i)                                           \
-    for (size_t j = 0; j < j_max; ++j)
-#define MATRIX_FOR_EACH(i, j) MATRIX_FOR_EACH_RANGE(i, R, j, C)
-
-namespace math { namespace matrix {
-
-  template <typename T, std::size_t R, std::size_t C> class matrix {
-  public:
-    using value_type = T;
-
-    template <typename M>
-    using mul_t = decltype(std::declval<T>() * std::declval<M>());
-    template <typename M>
-    using div_t = decltype(std::declval<T>() / std::declval<M>());
-
-  public:
-    matrix() = default;
-    matrix(std::array<std::array<T, C>, R> const & init) {
-      MATRIX_FOR_EACH(i, j) { _data[i][j] = init[i][j]; }
-    }
-
-    template <size_t N>
-    matrix(vector::vector<typename std::enable_if<C == 1 && N == R, T>::type,
-                          N> const & other) {
-      VECTOR_FOR_EACH(i) { _data[i][0] = other[i]; }
-    }
-    matrix(matrix const & other) { *this = other; }
-    matrix(matrix && other) { *this = std::move(other); }
-    matrix & operator=(matrix const & other) {
-      MATRIX_FOR_EACH(i, j) { _data[i][j] = other(i, j); }
-      return *this;
-    }
-    matrix & operator=(matrix && other) {
-      MATRIX_FOR_EACH(i, j) { _data[i][j] = std::move(other(i, j)); }
-      return *this;
-    }
-
-    template <size_t R2, size_t C2> matrix(matrix<T, R2, C2> const & other) {
-      MATRIX_FOR_EACH_RANGE(i, std::min(R, R2), j, std::min(C, C2)) {
-        _data[i][j] = other(i, j);
-      }
-    }
-
-    matrix<T, C, R> transpose() const {
-      matrix<T, C, R> out;
-      MATRIX_FOR_EACH(i, j) { out(j, i) = _data[i][j]; }
-      return out;
-    }
-
-    template <size_t C2>
-    matrix<T, R, C + C2> concat(matrix<T, R, C2> const & other,
-                                concat_strategy::horizontal_concat_t) const {
-      matrix<T, R, C + C2> accum{*this};
-      MATRIX_FOR_EACH_RANGE(i, R, j, C2) { accum(i, j + C) = other(i, j); }
-      return accum;
-    }
-
-    template <size_t R2>
-    matrix<T, R + R2, C> concat(matrix<T, R2, C> const & other,
-                                concat_strategy::vertical_concat_t) const {
-      matrix<T, R + R2, C> accum{*this};
-      MATRIX_FOR_EACH_RANGE(i, R2, j, C) { accum(i + R, j) = other(i, j); }
-      return accum;
-    }
-
-    template <size_t R2, size_t C2>
-    matrix<T, R + R2, C + C2> concat(matrix<T, R2, C2> const & other,
-                                     concat_strategy::diagonal_concat_t) const {
-      matrix<T, R + R2, C + C2> accum{*this};
-      MATRIX_FOR_EACH_RANGE(i, R2, j, C2) { accum(i + R, j + C) = other(i, j); }
-      return accum;
-    }
-
-    T const & operator()(std::size_t row, std::size_t col) const {
-      return _data[row][col];
-    }
-    T & operator()(std::size_t row, std::size_t col) { return _data[row][col]; }
-    row_reference<const T, C> operator[](std::size_t row) const {
-      return {_data[row]};
-    }
-    row_reference<T, C> operator[](std::size_t row) { return {_data[row]}; }
-    row_reference<const T, C> at(std::size_t row) const {
-      expects(row < R, std::out_of_range, "row index out of range");
-      return operator[](row);
-    }
-    row_reference<T, C> at(std::size_t row) {
-      expects(row < R, std::out_of_range, "row index out of range");
-      return operator[](row);
-    }
-    value_type const & at(std::size_t row, std::size_t col) const {
-      expects(row < R && col < C, std::out_of_range,
-              "coordinates out of range");
-      return _data[row][col];
-    }
-    value_type & at(std::size_t row, std::size_t col) {
-      expects(row < R && col < C, std::out_of_range,
-              "coordinates out of range");
-      return _data[row][col];
-    }
-
-    matrix & operator+=(matrix const & other) {
-      MATRIX_FOR_EACH(i, j) { _data[i][j] += other(i, j); }
-      return *this;
-    }
-    matrix operator+(matrix const & other) const {
-      return matrix{*this} += other;
-    }
-    matrix & operator-=(matrix const & other) {
-      MATRIX_FOR_EACH(i, j) { _data[i][j] -= other(i, j); }
-      return *this;
-    }
-    matrix operator-(matrix const & other) const {
-      return matrix{*this} -= other;
-    }
-
-    matrix operator-() const {
-      matrix tmp;
-      MATRIX_FOR_EACH(i, j) { tmp(i, j) = -_data[i][j]; }
-      return tmp;
-    }
-
-    vector::vector<T, C> operator*(vector::vector<T, C> const & vec) const {
-      vector::vector<T, C> rval;
-      MATRIX_FOR_EACH(i, j) { rval[i] += _data[i][j] * vec[j]; }
-      return rval;
-    }
-
-    template <std::size_t C2>
-    matrix<T, R, C2> operator*(matrix<T, C, C2> const & other) const {
-      matrix<T, R, C2> rval;
-      MATRIX_FOR_EACH(i, j) {
-        for (size_t k = 0; k < C2; ++k) {
-          rval(i, k) += _data[i][j] * other(j, k);
-        }
-      }
-      return rval;
-    }
-
-    matrix<T, R, C> & operator*=(T c) {
-      MATRIX_FOR_EACH(i, j) { _data[i][j] *= c; }
-      return *this;
-    }
-
-    template <typename M>
-    MATRIX_DISABLE_IF_MATRIX(M, mul_t<M>, R, C)
-    operator*(M c) const {
-      return matrix<mul_t<M>, R, C>{*this} *= c;
-    }
-
-    template <typename M>
-    friend MATRIX_DISABLE_IF_MATRIX(M, mul_t<M>, R, C)
-    operator*(M c, matrix const & matr) {
-      return matrix<mul_t<M>, R, C>{matr} *= c;
-    }
-
-    template <typename M> matrix<div_t<M>, R, C> & operator/=(M c) {
-      MATRIX_FOR_EACH(i, j) { _data[i][j] /= c; }
-      return *this;
-    }
-    template <typename M> matrix<div_t<M>, R, C> operator/(M c) const {
-      return matrix<mul_t<M>, R, C>{*this} /= c;
-    }
-
-    bool operator==(matrix const & other) const {
-      MATRIX_FOR_EACH(i, j) {
-        if (_data[i][j] != other(i, j)) { return false; }
+#include "math/matrix/macro.h"
+
+namespace math::matrix {
+
+template <typename T, std::size_t R, std::size_t C> class matrix {
+public:
+  using value_type = T;
+
+  template <typename M>
+  using mul_t = decltype(std::declval<T>() * std::declval<M>());
+  template <typename M>
+  using div_t = decltype(std::declval<T>() / std::declval<M>());
+
+public:
+  matrix() = default;
+
+  MATRIX_CTOR_N_ARGS(1) {}
+  MATRIX_CTOR_N_ARGS(2) {}
+  MATRIX_CTOR_N_ARGS(3) {}
+  MATRIX_CTOR_N_ARGS(4) {}
+
+  DEFER_RESOLUTION matrix(vector::vector<T, R> const & other,
+                          DEFERRED_ENABLE_IF_T(C == 1, bool) = true) {
+    VECTOR_FOR_EACH_RANGE(i, R) { _data[i][0] = other[i]; }
+  }
+
+  matrix(std::array<std::array<T, C>, R> const & init) {
+    MATRIX_FOR_EACH(i, j) { _data[i][j] = init[i][j]; }
+  }
+
+  matrix(std::array<vector::vector<T, C>, R> const & init) {
+    MATRIX_FOR_EACH(i, j) { _data[i][j] = init[i][j]; }
+  }
+
+  template <size_t R2, size_t C2> matrix(matrix<T, R2, C2> const & other) {
+    MATRIX_FOR_EACH_RANGE(i, std::min(R, R2), j, std::min(C, C2)) {
+      _data[i][j] = other(i, j);
+    }
+  }
+
+  matrix<T, C, R> transpose() const {
+    matrix<T, C, R> out;
+    MATRIX_FOR_EACH(i, j) { out(j, i) = _data[i][j]; }
+    return out;
+  }
+
+  template <size_t C2>
+  matrix<T, R, C + C2> concat(matrix<T, R, C2> const & other,
+                              concat_strategy::horizontal_concat_t) const {
+    matrix<T, R, C + C2> accum{*this};
+    MATRIX_FOR_EACH_RANGE(i, R, j, C2) { accum(i, j + C) = other(i, j); }
+    return accum;
+  }
+
+  template <size_t R2>
+  matrix<T, R + R2, C> concat(matrix<T, R2, C> const & other,
+                              concat_strategy::vertical_concat_t) const {
+    matrix<T, R + R2, C> accum{*this};
+    MATRIX_FOR_EACH_RANGE(i, R2, j, C) { accum(i + R, j) = other(i, j); }
+    return accum;
+  }
+
+  template <size_t R2, size_t C2>
+  matrix<T, R + R2, C + C2> concat(matrix<T, R2, C2> const & other,
+                                   concat_strategy::diagonal_concat_t) const {
+    matrix<T, R + R2, C + C2> accum{*this};
+    MATRIX_FOR_EACH_RANGE(i, R2, j, C2) { accum(i + R, j + C) = other(i, j); }
+    return accum;
+  }
+
+  // In C++23 - we can use operator[](row, col)
+  T const & operator()(std::size_t row, std::size_t col) const {
+    return _data[row][col];
+  }
+  T & operator()(std::size_t row, std::size_t col) { return _data[row][col]; }
+  row_reference<const T, C> operator[](std::size_t row) const {
+    return {_data[row]};
+  }
+  row_reference<T, C> operator[](std::size_t row) { return {_data[row]}; }
+  row_reference<const T, C> at(std::size_t row) const {
+    expects(row < R, std::out_of_range, "row index out of range");
+    return operator[](row);
+  }
+  row_reference<T, C> at(std::size_t row) {
+    expects(row < R, std::out_of_range, "row index out of range");
+    return operator[](row);
+  }
+  value_type const & at(std::size_t row, std::size_t col) const {
+    expects(row < R && col < C, std::out_of_range, "coordinates out of range");
+    return _data[row][col];
+  }
+  value_type & at(std::size_t row, std::size_t col) {
+    expects(row < R && col < C, std::out_of_range, "coordinates out of range");
+    return _data[row][col];
+  }
+
+  matrix & operator+=(matrix const & other) {
+    MATRIX_FOR_EACH(i, j) { _data[i][j] += other(i, j); }
+    return *this;
+  }
+  matrix operator+(matrix const & other) const {
+    return matrix{*this} += other;
+  }
+  matrix & operator-=(matrix const & other) {
+    MATRIX_FOR_EACH(i, j) { _data[i][j] -= other(i, j); }
+    return *this;
+  }
+  matrix operator-(matrix const & other) const {
+    return matrix{*this} -= other;
+  }
+
+  matrix operator-() const {
+    matrix tmp;
+    MATRIX_FOR_EACH(i, j) { tmp(i, j) = -_data[i][j]; }
+    return tmp;
+  }
+
+  vector::vector<T, C> operator*(vector::vector<T, C> const & vec) const {
+    vector::vector<T, C> rval;
+    MATRIX_FOR_EACH(i, j) { rval[i] += _data[i][j] * vec[j]; }
+    return rval;
+  }
+
+  template <std::size_t C2>
+  matrix<T, R, C2> operator*(matrix<T, C, C2> const & other) const {
+    matrix<T, R, C2> rval;
+    MATRIX_FOR_EACH(i, j) {
+      for (size_t k = 0; k < C2; ++k) {
+        rval(i, k) += _data[i][j] * other(j, k);
       }
-      return true;
     }
-    bool operator!=(matrix const & other) const { return !operator==(other); }
-
-  private:
-    value_type _data[R][C] = {value_type()};
-  };
-
-}}
+    return rval;
+  }
+
+  matrix<T, R, C> & operator*=(T c) {
+    MATRIX_FOR_EACH(i, j) { _data[i][j] *= c; }
+    return *this;
+  }
+
+  template <typename M>
+  MATRIX_DISABLE_IF_MATRIX(M, mul_t<M>, R, C)
+  operator*(M c) const {
+    return matrix<mul_t<M>, R, C>{*this} *= c;
+  }
+
+  template <typename M>
+  friend MATRIX_DISABLE_IF_MATRIX(M, mul_t<M>, R, C)
+  operator*(M c, matrix const & matr) {
+    return matrix<mul_t<M>, R, C>{matr} *= c;
+  }
+
+  template <typename M> matrix<div_t<M>, R, C> & operator/=(M c) {
+    MATRIX_FOR_EACH(i, j) { _data[i][j] /= c; }
+    return *this;
+  }
+  template <typename M> matrix<div_t<M>, R, C> operator/(M c) const {
+    return matrix<mul_t<M>, R, C>{*this} /= c;
+  }
+
+  bool operator==(matrix const & other) const {
+    MATRIX_FOR_EACH(i, j) {
+      if (_data[i][j] != other(i, j)) { return false; }
+    }
+    return true;
+  }
+  bool operator!=(matrix const & other) const { return !operator==(other); }
+
+private:
+  value_type _data[R][C] = {value_type()};
+};
+
+MATRIX_CTOR_DEDUCTION(1);
+MATRIX_CTOR_DEDUCTION(2);
+MATRIX_CTOR_DEDUCTION(3);
+MATRIX_CTOR_DEDUCTION(4);
+
+}
+
+#include "math/matrix/undef.h"

+ 83 - 82
include/math/matrix/matrix_helpers.hpp

@@ -9,85 +9,86 @@
 
 #include "math/matrix/matrix.hpp"
 
-namespace math { namespace matrix {
-
-  template <typename T, std::size_t N> matrix<T, N, N> identity() {
-    matrix<T, N, N> rval;
-    VECTOR_FOR_EACH(i) { rval.at(i, i) = 1; }
-    return rval;
-  }
-
-  template <typename T, std::size_t N>
-  matrix<T, N, N> diagonal(vector::vector<T, N> const & vec) {
-    matrix<T, N, N> rval = identity<T, N>();
-    VECTOR_FOR_EACH(i) { rval.at(i, i) = vec[i]; }
-    return rval;
-  }
-
-  template <typename T, std::size_t N>
-  matrix<T, N + 1, N + 1> translation(vector::vector<T, N> const & vec) {
-    matrix<T, N + 1, N + 1> rval = identity<T, N + 1>();
-    VECTOR_FOR_EACH(i) { rval.at(i, N) = vec[i]; }
-    return rval;
-  }
-
-  template <typename T, std::size_t N>
-  matrix<T, N + 1, N + 1> scalar(vector::vector<T, N> const & vec) {
-    matrix<T, N + 1, N + 1> rval = identity<T, N + 1>();
-    VECTOR_FOR_EACH(i) { rval.at(i, i) = vec[i]; }
-    return rval;
-  }
-
-  template <size_t N> struct rotation_t {
-    constexpr rotation_t(size_t f, size_t s) : first(f), second(s) {}
-    size_t first, second;
-  };
-
-  namespace rotate {
-    constexpr rotation_t<3> const X_AXIS{1, 2};
-    constexpr rotation_t<3> const Y_AXIS{2, 0};
-    constexpr rotation_t<3> const Z_AXIS{0, 1};
-
-    constexpr rotation_t<3> const ROLL{1, 2};
-    constexpr rotation_t<3> const PITCH{2, 0};
-    constexpr rotation_t<3> const YAW{0, 1};
-
-    constexpr rotation_t<2> const ROT_2D{0, 1};
-  }
-
-  template <typename T, size_t D>
-  auto rotation(T theta, rotation_t<D> r)
-      -> matrix<decltype(sin(theta)), D, D> {
-    static_assert(D >= 2, "cannot rotate with 1D matrix");
-    using G = decltype(sin(theta));
-    using std::cos;
-    using std::sin;
-    matrix<G, D, D> rval = identity<G, D>();
-    G const vsin = sin(theta);
-    G const vcos = cos(theta);
-    rval.at(r.first, r.first) = vcos;
-    rval.at(r.second, r.second) = vcos;
-    rval.at(r.first, r.second) = -vsin;
-    rval.at(r.second, r.first) = vsin;
-    return rval;
-  }
-
-  template <size_t D, typename T, size_t RD>
-  auto rotation(T theta, rotation_t<RD> r)
-      -> matrix<decltype(sin(theta)), D, D> {
-    static_assert(D > RD,
-                  "rotation<D> has to increase the number of dimensions");
-    using G = decltype(sin(theta));
-    return rotation(theta, r).concat(identity<G, D - RD>(),
-                                     concat_strategy::diagonal);
-  }
-
-  template <typename T, size_t N>
-  vector::vector<T, N> operator*(matrix<T, N + 1, N + 1> const & lhs,
-                                 vector::vector<T, N> const & rhs) {
-    vector::vector<T, N + 1> tmp(rhs);
-    tmp[N] = 1;
-    return vector::vector<T, N>(lhs * tmp);
-  }
-
-}}
+#include "math/matrix/macro.h"
+
+namespace math::matrix {
+
+template <typename T, std::size_t N> matrix<T, N, N> identity() {
+  matrix<T, N, N> rval;
+  VECTOR_FOR_EACH(i) { rval.at(i, i) = 1; }
+  return rval;
+}
+
+template <typename T, std::size_t N>
+matrix<T, N, N> diagonal(vector::vector<T, N> const & vec) {
+  matrix<T, N, N> rval = identity<T, N>();
+  VECTOR_FOR_EACH(i) { rval.at(i, i) = vec[i]; }
+  return rval;
+}
+
+template <typename T, std::size_t N>
+matrix<T, N + 1, N + 1> translation(vector::vector<T, N> const & vec) {
+  matrix<T, N + 1, N + 1> rval = identity<T, N + 1>();
+  VECTOR_FOR_EACH(i) { rval.at(i, N) = vec[i]; }
+  return rval;
+}
+
+template <typename T, std::size_t N>
+matrix<T, N + 1, N + 1> scalar(vector::vector<T, N> const & vec) {
+  matrix<T, N + 1, N + 1> rval = identity<T, N + 1>();
+  VECTOR_FOR_EACH(i) { rval.at(i, i) = vec[i]; }
+  return rval;
+}
+
+template <size_t N> struct rotation_t {
+  constexpr rotation_t(size_t f, size_t s) : first(f), second(s) {}
+  size_t first, second;
+};
+
+namespace rotate {
+constexpr rotation_t<3> const X_AXIS{1, 2};
+constexpr rotation_t<3> const Y_AXIS{2, 0};
+constexpr rotation_t<3> const Z_AXIS{0, 1};
+
+constexpr rotation_t<3> const ROLL{1, 2};
+constexpr rotation_t<3> const PITCH{2, 0};
+constexpr rotation_t<3> const YAW{0, 1};
+
+constexpr rotation_t<2> const ROT_2D{0, 1};
+}
+
+template <typename T, size_t D>
+auto rotation(T theta, rotation_t<D> r) -> matrix<decltype(sin(theta)), D, D> {
+  static_assert(D >= 2, "cannot rotate with 1D matrix");
+  using G = decltype(sin(theta));
+  using std::cos;
+  using std::sin;
+  matrix<G, D, D> rval = identity<G, D>();
+  G const vsin = sin(theta);
+  G const vcos = cos(theta);
+  rval.at(r.first, r.first) = vcos;
+  rval.at(r.second, r.second) = vcos;
+  rval.at(r.first, r.second) = -vsin;
+  rval.at(r.second, r.first) = vsin;
+  return rval;
+}
+
+template <size_t D, typename T, size_t RD>
+auto rotation(T theta, rotation_t<RD> r) -> matrix<decltype(sin(theta)), D, D> {
+  static_assert(D > RD, "rotation<D> has to increase the number of dimensions");
+  using G = decltype(sin(theta));
+  return rotation(theta, r).concat(identity<G, D - RD>(),
+                                   concat_strategy::diagonal);
+}
+
+template <typename T, size_t N>
+vector::vector<T, N> operator*(matrix<T, N + 1, N + 1> const & lhs,
+                               vector::vector<T, N> const & rhs) {
+  vector::vector<T, N + 1> tmp(rhs);
+  tmp[N] = 1;
+  return vector::vector<T, N>(lhs * tmp);
+}
+
+}
+
+#include "math/matrix/undef.h"

+ 35 - 31
include/math/matrix/row_reference.hpp

@@ -6,34 +6,38 @@
 #include <expect/expect.hpp>
 #include <math/vector/vector.hpp>
 
-namespace math { namespace matrix {
-  template <typename T, size_t C> class row_reference {
-  private:
-    row_reference(T (&h)[C]) : _handle(h) {}
-
-  public:
-    row_reference(row_reference const &) = delete;
-    row_reference(row_reference &&) = default;
-
-    template <typename S>
-    row_reference & operator=(row_reference<S, C> const & other) {
-      VECTOR_FOR_EACH_RANGE(i, C) { _handle[i] = other[i]; }
-      return *this;
-    }
-
-    T const & operator[](size_t col) const { return _handle[col]; }
-    T & operator[](size_t col) { return _handle[col]; }
-    T const & at(size_t col) const {
-      expects(col < C, std::out_of_range, "column index out of range");
-      return operator[](col);
-    }
-    T & at(size_t col) {
-      expects(col < C, std::out_of_range, "column index out of range");
-      return operator[](col);
-    }
-
-  private:
-    template <typename _T, size_t R, size_t _C> friend class matrix;
-    T (&_handle)[C];
-  };
-}}
+#include "math/matrix/macro.h"
+
+namespace math::matrix {
+template <typename T, size_t C> class row_reference {
+private:
+  row_reference(T (&h)[C]) : _handle(h) {}
+
+public:
+  row_reference(row_reference const &) = delete;
+  row_reference(row_reference &&) = default;
+
+  template <typename S>
+  row_reference & operator=(row_reference<S, C> const & other) {
+    VECTOR_FOR_EACH_RANGE(i, C) { _handle[i] = other[i]; }
+    return *this;
+  }
+
+  T const & operator[](size_t col) const { return _handle[col]; }
+  T & operator[](size_t col) { return _handle[col]; }
+  T const & at(size_t col) const {
+    expects(col < C, std::out_of_range, "column index out of range");
+    return operator[](col);
+  }
+  T & at(size_t col) {
+    expects(col < C, std::out_of_range, "column index out of range");
+    return operator[](col);
+  }
+
+private:
+  template <typename _T, size_t R, size_t _C> friend class matrix;
+  T (&_handle)[C];
+};
+}
+
+#include "math/matrix/undef.h"

+ 13 - 11
include/math/matrix/traits.hpp

@@ -2,18 +2,20 @@
 
 #include "math/matrix/forward.h"
 
-namespace math { namespace matrix {
+namespace math::matrix {
 
-  template <typename T> struct is_matrix {
-    static const constexpr bool value = false;
-  };
+template <typename T> struct is_matrix {
+  static const constexpr bool value = false;
+};
 
-  template <typename T, size_t R, size_t C> struct is_matrix<matrix<T, R, C>> {
-    static const constexpr bool value = true;
-  };
+template <typename T, size_t R, size_t C> struct is_matrix<matrix<T, R, C>> {
+  static const constexpr bool value = true;
+};
 
-  template <typename T, size_t N> struct is_matrix<vector::vector<T, N>> {
-    static const constexpr bool value = true;
-  };
+template <typename T, size_t N> struct is_matrix<vector::vector<T, N>> {
+  static const constexpr bool value = true;
+};
 
-}}
+template <typename T> constexpr bool is_matrix_v = is_matrix<T>::value;
+
+}

+ 16 - 0
include/math/matrix/undef.h

@@ -0,0 +1,16 @@
+//
+//  undef.h
+//  matrix
+//
+//  Created by Sam Jaffe on 2/17/24.
+//  Copyright © 2024 Sam Jaffe. All rights reserved.
+//
+
+#undef MATRIX_DISABLE_IF_MATRIX
+#undef MATRIX_FOR_EACH_RANGE
+#undef MATRIX_FOR_EACH
+
+#undef MATRIX_CTOR_N_ARGS
+#undef MATRIX_CTOR_DEDUCTION
+
+#include <math/vector/undef.h>

+ 119 - 64
matrix.xcodeproj/project.pbxproj

@@ -49,47 +49,47 @@
 			remoteGlobalIDString = 05818F851A685AEA0072A469;
 			remoteInfo = GoogleMock;
 		};
-		CDAE62532B77B8DF00551FB8 /* PBXContainerItemProxy */ = {
+		CDCA9B992B7856C900C8B1EC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
-			containerPortal = CDAE624E2B77B8DF00551FB8 /* vector.xcodeproj */;
+			containerPortal = CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */;
 			proxyType = 2;
-			remoteGlobalIDString = CD10425824E837FB00C0DF2A;
-			remoteInfo = "vector-test";
+			remoteGlobalIDString = CDD476BD29C5423B00BDB829;
+			remoteInfo = expect;
 		};
-		CDAE62552B77B8DF00551FB8 /* PBXContainerItemProxy */ = {
+		CDCA9B9B2B7856C900C8B1EC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
-			containerPortal = CDAE624E2B77B8DF00551FB8 /* vector.xcodeproj */;
+			containerPortal = CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */;
 			proxyType = 2;
-			remoteGlobalIDString = CDAE62452B77B8A800551FB8;
-			remoteInfo = vector;
+			remoteGlobalIDString = CDEC1E8A235248390091D9F2;
+			remoteInfo = "expect-test";
 		};
-		CDAE62572B77B8E600551FB8 /* PBXContainerItemProxy */ = {
+		CDCA9BA52B7856EC00C8B1EC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
-			containerPortal = CDAE624E2B77B8DF00551FB8 /* vector.xcodeproj */;
-			proxyType = 1;
-			remoteGlobalIDString = CDAE62442B77B8A800551FB8;
-			remoteInfo = vector;
+			containerPortal = CDCA9B9D2B7856EC00C8B1EC /* vector.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CD10425824E837FB00C0DF2A;
+			remoteInfo = "vector-test";
 		};
-		CDAE62DB2B77BAAB00551FB8 /* PBXContainerItemProxy */ = {
+		CDCA9BA72B7856EC00C8B1EC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
-			containerPortal = CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */;
+			containerPortal = CDCA9B9D2B7856EC00C8B1EC /* vector.xcodeproj */;
 			proxyType = 2;
-			remoteGlobalIDString = CDD476BD29C5423B00BDB829;
-			remoteInfo = expect;
+			remoteGlobalIDString = CDAE62452B77B8A800551FB8;
+			remoteInfo = vector;
 		};
-		CDAE62DD2B77BAAB00551FB8 /* PBXContainerItemProxy */ = {
+		CDCA9BAF2B7856F300C8B1EC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
-			containerPortal = CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */;
+			containerPortal = CDCA9BA92B7856F300C8B1EC /* test-helpers.xcodeproj */;
 			proxyType = 2;
-			remoteGlobalIDString = CDEC1E8A235248390091D9F2;
-			remoteInfo = "expect-test";
+			remoteGlobalIDString = CD6030FC2AF67772000D9F31;
+			remoteInfo = "test-helpers";
 		};
-		CDAE62DF2B77BAB900551FB8 /* PBXContainerItemProxy */ = {
+		CDCA9BB12B7856F300C8B1EC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
-			containerPortal = CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */;
-			proxyType = 1;
-			remoteGlobalIDString = CDD476BC29C5423B00BDB829;
-			remoteInfo = expect;
+			containerPortal = CDCA9BA92B7856F300C8B1EC /* test-helpers.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDD965FA2B654D090091D92C;
+			remoteInfo = "test-helpers-test";
 		};
 /* End PBXContainerItemProxy section */
 
@@ -101,8 +101,16 @@
 		CD10426E24E8756100C0DF2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		CD10426F24E8757B00C0DF2A /* math */ = {isa = PBXFileReference; lastKnownFileType = folder; name = math; path = include/math; sourceTree = "<group>"; };
 		CDAE62392B77B88A00551FB8 /* libmatrix.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libmatrix.a; sourceTree = BUILT_PRODUCTS_DIR; };
-		CDAE624E2B77B8DF00551FB8 /* vector.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = vector.xcodeproj; path = ../vector/vector.xcodeproj; sourceTree = "<group>"; };
-		CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = expect.xcodeproj; path = ../expect/expect.xcodeproj; sourceTree = "<group>"; };
+		CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = expect.xcodeproj; path = external/expect/expect.xcodeproj; sourceTree = "<group>"; };
+		CDB6F8CD2B81011D00FE3226 /* row_reference.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = row_reference.hpp; sourceTree = "<group>"; };
+		CDB6F8CE2B81011D00FE3226 /* traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = traits.hpp; sourceTree = "<group>"; };
+		CDB6F8CF2B81011D00FE3226 /* matrix.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = matrix.hpp; sourceTree = "<group>"; };
+		CDB6F8D02B81011D00FE3226 /* matrix_helpers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = matrix_helpers.hpp; sourceTree = "<group>"; };
+		CDB6F8D12B81011D00FE3226 /* forward.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = forward.h; sourceTree = "<group>"; };
+		CDB6F8DB2B81012D00FE3226 /* macro.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macro.h; sourceTree = "<group>"; };
+		CDB6F8DC2B81014900FE3226 /* undef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = undef.h; sourceTree = "<group>"; };
+		CDCA9B9D2B7856EC00C8B1EC /* vector.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = vector.xcodeproj; path = external/vector/vector.xcodeproj; sourceTree = "<group>"; };
+		CDCA9BA92B7856F300C8B1EC /* test-helpers.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "test-helpers.xcodeproj"; path = "external/test-helpers/test-helpers.xcodeproj"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -139,9 +147,11 @@
 			isa = PBXGroup;
 			children = (
 				CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */,
-				CDAE624E2B77B8DF00551FB8 /* vector.xcodeproj */,
+				CDCA9B9D2B7856EC00C8B1EC /* vector.xcodeproj */,
+				CDCA9BA92B7856F300C8B1EC /* test-helpers.xcodeproj */,
 				CD0C59BD20C4127400454F82 /* GoogleMock.xcodeproj */,
 				CD10426F24E8757B00C0DF2A /* math */,
+				CDB6F8CA2B81011D00FE3226 /* include */,
 				CD7820301E44F42B001D22E5 /* test */,
 				CD10426D24E8756100C0DF2A /* matrix-test */,
 				CD0E427A1D9B38A9002FFED1 /* Products */,
@@ -174,20 +184,59 @@
 			path = test;
 			sourceTree = "<group>";
 		};
-		CDAE624F2B77B8DF00551FB8 /* Products */ = {
+		CDB6F8CA2B81011D00FE3226 /* include */ = {
+			isa = PBXGroup;
+			children = (
+				CDB6F8CB2B81011D00FE3226 /* math */,
+			);
+			path = include;
+			sourceTree = "<group>";
+		};
+		CDB6F8CB2B81011D00FE3226 /* math */ = {
+			isa = PBXGroup;
+			children = (
+				CDB6F8CC2B81011D00FE3226 /* matrix */,
+			);
+			path = math;
+			sourceTree = "<group>";
+		};
+		CDB6F8CC2B81011D00FE3226 /* matrix */ = {
+			isa = PBXGroup;
+			children = (
+				CDB6F8CD2B81011D00FE3226 /* row_reference.hpp */,
+				CDB6F8CE2B81011D00FE3226 /* traits.hpp */,
+				CDB6F8CF2B81011D00FE3226 /* matrix.hpp */,
+				CDB6F8D02B81011D00FE3226 /* matrix_helpers.hpp */,
+				CDB6F8D12B81011D00FE3226 /* forward.h */,
+				CDB6F8DB2B81012D00FE3226 /* macro.h */,
+				CDB6F8DC2B81014900FE3226 /* undef.h */,
+			);
+			path = matrix;
+			sourceTree = "<group>";
+		};
+		CDCA9B952B7856C900C8B1EC /* Products */ = {
 			isa = PBXGroup;
 			children = (
-				CDAE62542B77B8DF00551FB8 /* vector-test.xctest */,
-				CDAE62562B77B8DF00551FB8 /* libvector.a */,
+				CDCA9B9A2B7856C900C8B1EC /* libexpect.a */,
+				CDCA9B9C2B7856C900C8B1EC /* expect-test.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
 		};
-		CDAE62D72B77BAAB00551FB8 /* Products */ = {
+		CDCA9B9E2B7856EC00C8B1EC /* Products */ = {
 			isa = PBXGroup;
 			children = (
-				CDAE62DC2B77BAAB00551FB8 /* libexpect.a */,
-				CDAE62DE2B77BAAB00551FB8 /* expect-test.xctest */,
+				CDCA9BA62B7856EC00C8B1EC /* vector-test.xctest */,
+				CDCA9BA82B7856EC00C8B1EC /* libvector.a */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		CDCA9BAA2B7856F300C8B1EC /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CDCA9BB02B7856F300C8B1EC /* libtest-helpers.a */,
+				CDCA9BB22B7856F300C8B1EC /* test-helpers-test.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -235,8 +284,6 @@
 			buildRules = (
 			);
 			dependencies = (
-				CDAE62E02B77BAB900551FB8 /* PBXTargetDependency */,
-				CDAE62582B77B8E600551FB8 /* PBXTargetDependency */,
 			);
 			name = matrix;
 			productName = matrix;
@@ -274,7 +321,7 @@
 			projectDirPath = "";
 			projectReferences = (
 				{
-					ProductGroup = CDAE62D72B77BAAB00551FB8 /* Products */;
+					ProductGroup = CDCA9B952B7856C900C8B1EC /* Products */;
 					ProjectRef = CDAE62D62B77BAAB00551FB8 /* expect.xcodeproj */;
 				},
 				{
@@ -282,8 +329,12 @@
 					ProjectRef = CD0C59BD20C4127400454F82 /* GoogleMock.xcodeproj */;
 				},
 				{
-					ProductGroup = CDAE624F2B77B8DF00551FB8 /* Products */;
-					ProjectRef = CDAE624E2B77B8DF00551FB8 /* vector.xcodeproj */;
+					ProductGroup = CDCA9BAA2B7856F300C8B1EC /* Products */;
+					ProjectRef = CDCA9BA92B7856F300C8B1EC /* test-helpers.xcodeproj */;
+				},
+				{
+					ProductGroup = CDCA9B9E2B7856EC00C8B1EC /* Products */;
+					ProjectRef = CDCA9B9D2B7856EC00C8B1EC /* vector.xcodeproj */;
 				},
 			);
 			projectRoot = "";
@@ -323,32 +374,46 @@
 			remoteRef = CD0C59CA20C4127400454F82 /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
-		CDAE62542B77B8DF00551FB8 /* vector-test.xctest */ = {
+		CDCA9B9A2B7856C900C8B1EC /* libexpect.a */ = {
+			isa = PBXReferenceProxy;
+			fileType = archive.ar;
+			path = libexpect.a;
+			remoteRef = CDCA9B992B7856C900C8B1EC /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CDCA9B9C2B7856C900C8B1EC /* expect-test.xctest */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.cfbundle;
+			path = "expect-test.xctest";
+			remoteRef = CDCA9B9B2B7856C900C8B1EC /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CDCA9BA62B7856EC00C8B1EC /* vector-test.xctest */ = {
 			isa = PBXReferenceProxy;
 			fileType = wrapper.cfbundle;
 			path = "vector-test.xctest";
-			remoteRef = CDAE62532B77B8DF00551FB8 /* PBXContainerItemProxy */;
+			remoteRef = CDCA9BA52B7856EC00C8B1EC /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
-		CDAE62562B77B8DF00551FB8 /* libvector.a */ = {
+		CDCA9BA82B7856EC00C8B1EC /* libvector.a */ = {
 			isa = PBXReferenceProxy;
 			fileType = archive.ar;
 			path = libvector.a;
-			remoteRef = CDAE62552B77B8DF00551FB8 /* PBXContainerItemProxy */;
+			remoteRef = CDCA9BA72B7856EC00C8B1EC /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
-		CDAE62DC2B77BAAB00551FB8 /* libexpect.a */ = {
+		CDCA9BB02B7856F300C8B1EC /* libtest-helpers.a */ = {
 			isa = PBXReferenceProxy;
 			fileType = archive.ar;
-			path = libexpect.a;
-			remoteRef = CDAE62DB2B77BAAB00551FB8 /* PBXContainerItemProxy */;
+			path = "libtest-helpers.a";
+			remoteRef = CDCA9BAF2B7856F300C8B1EC /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
-		CDAE62DE2B77BAAB00551FB8 /* expect-test.xctest */ = {
+		CDCA9BB22B7856F300C8B1EC /* test-helpers-test.xctest */ = {
 			isa = PBXReferenceProxy;
 			fileType = wrapper.cfbundle;
-			path = "expect-test.xctest";
-			remoteRef = CDAE62DD2B77BAAB00551FB8 /* PBXContainerItemProxy */;
+			path = "test-helpers-test.xctest";
+			remoteRef = CDCA9BB12B7856F300C8B1EC /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
 /* End PBXReferenceProxy section */
@@ -409,16 +474,6 @@
 			name = GoogleMock;
 			targetProxy = CD0C59DC20C412BF00454F82 /* PBXContainerItemProxy */;
 		};
-		CDAE62582B77B8E600551FB8 /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			name = vector;
-			targetProxy = CDAE62572B77B8E600551FB8 /* PBXContainerItemProxy */;
-		};
-		CDAE62E02B77BAB900551FB8 /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			name = expect;
-			targetProxy = CDAE62DF2B77BAB900551FB8 /* PBXContainerItemProxy */;
-		};
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */
@@ -449,7 +504,7 @@
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -507,7 +562,7 @@
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -558,7 +613,7 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
@@ -578,7 +633,7 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;

+ 77 - 0
matrix.xcodeproj/xcshareddata/xcschemes/matrix.xcscheme

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1340"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "CDAE62382B77B88A00551FB8"
+               BuildableName = "libmatrix.a"
+               BlueprintName = "matrix"
+               ReferencedContainer = "container:matrix.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "CD0C59D020C412AD00454F82"
+               BuildableName = "matrix-test.xctest"
+               BlueprintName = "matrix-test"
+               ReferencedContainer = "container:matrix.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CDAE62382B77B88A00551FB8"
+            BuildableName = "libmatrix.a"
+            BlueprintName = "matrix"
+            ReferencedContainer = "container:matrix.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 27 - 25
test/matrix_helper_test.cxx

@@ -5,26 +5,28 @@
 //  Created by Sam Jaffe on 6/3/18.
 //
 
-#include <gmock/gmock.h>
+#include <testing/xcode_gtest_helper.h>
 
 #include "math/matrix/matrix_helpers.hpp"
 
+#include "math/matrix/macro.h"
+
 template <std::size_t N> using vec = math::vector::vector<double, N>;
 
 namespace math { namespace matrix {
-  template <typename T, std::size_t R, std::size_t C>
-  void PrintTo(matrix<T, R, C> const & matr, std::ostream * out) {
-    (*out) << "[\n";
-    VECTOR_FOR_EACH_RANGE(i, R) {
-      (*out) << "  [ ";
-      VECTOR_FOR_EACH_RANGE(j, C) {
-        if (j != 0) (*out) << ", ";
-        (*out) << matr(i, j);
-      }
-      (*out) << " ]\n";
+template <typename T, std::size_t R, std::size_t C>
+void PrintTo(matrix<T, R, C> const & matr, std::ostream * out) {
+  (*out) << "[\n";
+  VECTOR_FOR_EACH_RANGE(i, R) {
+    (*out) << "  [ ";
+    VECTOR_FOR_EACH_RANGE(j, C) {
+      if (j != 0) (*out) << ", ";
+      (*out) << matr(i, j);
     }
-    (*out) << "]";
+    (*out) << " ]\n";
   }
+  (*out) << "]";
+}
 }}
 
 TEST(MatrixHelper, IdentityFunctionProducesOnes) {
@@ -36,7 +38,7 @@ TEST(MatrixHelper, IdentityFunctionProducesOnes) {
 }
 
 TEST(MatrixHelper, DiagonalWillFillWithAllZeros) {
-  vec<4> const vector({1.5, 0.5, -1.0, 2.2});
+  vec<4> const vector{1.5, 0.5, -1.0, 2.2};
   auto diag = math::matrix::diagonal(vector);
   VECTOR_FOR_EACH_RANGE(i, 4) { EXPECT_THAT(diag.at(i, i), vector.at(i)); }
   MATRIX_FOR_EACH_RANGE(i, 4, j, 4) {
@@ -45,15 +47,15 @@ TEST(MatrixHelper, DiagonalWillFillWithAllZeros) {
 }
 
 TEST(MatrixHelper, ScalarMatrixPiecewiseStretchesVector) {
-  vec<3> const vector({1.5, 0.5, -1.0});
-  vec<3> const x({2.0, 3.8, 11.0});
+  vec<3> const vector{1.5, 0.5, -1.0};
+  vec<3> const x{2.0, 3.8, 11.0};
   auto const A = math::matrix::scalar(vector);
   EXPECT_THAT(A * x, vector * x);
 }
 
 TEST(MatrixHelper, TranslationMatrixAddsToEachElement) {
-  vec<3> const vector({1.5, 0.5, -1.0});
-  vec<3> const x({2.0, 3.8, 11.0});
+  vec<3> const vector{1.5, 0.5, -1.0};
+  vec<3> const x{2.0, 3.8, 11.0};
   auto const A = math::matrix::translation(vector);
   EXPECT_THAT(A * x, vector + x);
 }
@@ -69,7 +71,7 @@ class RotationTest
 TEST(MatrixHelper, RotateByHalfPi) {
   auto const theta = M_PI / 2;
   auto const iden = math::matrix::identity<double, 2>();
-  auto const expected = math::matrix::matrix<double, 2, 2>({{{0, -1}, {1, 0}}});
+  auto const expected = math::matrix::matrix<int, 2, 2>{{0, -1}, {1, 0}};
 
   auto result = iden * rotation(theta, rotate::ROT_2D);
   MATRIX_FOR_EACH_RANGE(i, 2, j, 2) {
@@ -95,8 +97,8 @@ TEST_P(RotationTest, RotateByHalfPi) {
 TEST(MatrixHelper, RotateIntoLargerMatrixArea) {
   auto const theta = M_PI / 2;
   auto const iden = math::matrix::identity<double, 4>();
-  auto const expected = math::matrix::matrix<double, 4, 4>(
-      {{{0, -1, 0, 0}, {1, 0, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}});
+  auto const expected = math::matrix::matrix<double, 4, 4>{
+      {0, -1, 0, 0}, {1, 0, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
 
   auto result = iden * rotation<4>(theta, rotate::Z_AXIS);
   MATRIX_FOR_EACH_RANGE(i, 4, j, 4) {
@@ -107,8 +109,8 @@ TEST(MatrixHelper, RotateIntoLargerMatrixArea) {
 }
 
 std::vector<std::tuple<rotation_t<3>, matr3>> values{
-    {rotate::X_AXIS, matr3({{{1, 0, 0}, {0, 0, -1}, {0, 1, 0}}})},
-    {rotate::Y_AXIS, matr3({{{0, 0, 1}, {0, 1, 0}, {-1, 0, 0}}})},
-    {rotate::Z_AXIS, matr3({{{0, -1, 0}, {1, 0, 0}, {0, 0, 1}}})}};
-INSTANTIATE_TEST_CASE_P(MatrixHelper, RotationTest,
-                        ::testing::ValuesIn(values));
+    {rotate::X_AXIS, matr3{{1, 0, 0}, {0, 0, -1}, {0, 1, 0}}},
+    {rotate::Y_AXIS, matr3{{0, 0, 1}, {0, 1, 0}, {-1, 0, 0}}},
+    {rotate::Z_AXIS, matr3{{0, -1, 0}, {1, 0, 0}, {0, 0, 1}}}};
+INSTANTIATE_TEST_SUITE_P(MatrixHelper, RotationTest,
+                         ::testing::ValuesIn(values));

+ 42 - 39
test/matrix_test.cxx

@@ -5,19 +5,19 @@
 //  Created by Sam Jaffe on 6/3/18.
 //
 
-#include <gmock/gmock.h>
+#include <testing/xcode_gtest_helper.h>
 
 #include "math/matrix/matrix.hpp"
 
 using matr2i = math::matrix::matrix<int, 2, 2>;
 using matr2 = math::matrix::matrix<double, 2, 2>;
 
-matr2i identity2i() { return matr2i({{{1, 0}, {0, 1}}}); }
-matr2 identity2() { return matr2({{{1, 0}, {0, 1}}}); }
+matr2i identity2i() { return matr2i{{1, 0}, {0, 1}}; }
+matr2 identity2() { return matr2{{1, 0}, {0, 1}}; }
 
 TEST(Matrix, Equality) {
-  EXPECT_THAT(identity2i(), matr2i({{{1, 0}, {0, 1}}}));
-  EXPECT_THAT(identity2i(), ::testing::Ne(matr2i({{{0, 1}, {0, 1}}})));
+  EXPECT_THAT(identity2i(), (matr2i{{1, 0}, {0, 1}}));
+  EXPECT_THAT(identity2i(), ::testing::Ne(matr2i{{0, 1}, {0, 1}}));
 }
 
 TEST(Matrix, AtRowBeyondBoundaryThrowsException) {
@@ -53,25 +53,25 @@ TEST(Matrix, ConstAtColumnBeyondBoundaryThrowsException) {
 }
 
 TEST(Matrix, TransposeOfSquareMatrixRotatesAroundDiagonal) {
-  matr2i mat({{{1, 1}, {2, 2}}});
-  EXPECT_THAT(mat.transpose(), matr2i({{{1, 2}, {1, 2}}}));
+  matr2i mat{{1, 1}, {2, 2}};
+  EXPECT_THAT(mat.transpose(), (matr2i{{1, 2}, {1, 2}}));
 }
 
 TEST(Matrix, TransposeOfMatrixFlipsDimension) {
   using matr3x2i = math::matrix::matrix<int, 3, 2>;
   using matr2x3i = math::matrix::matrix<int, 2, 3>;
-  matr3x2i mat({{{1, 1}, {2, 2}, {3, 3}}});
-  EXPECT_THAT(mat.transpose(), matr2x3i({{{1, 2, 3}, {1, 2, 3}}}));
+  matr3x2i mat{{1, 1}, {2, 2}, {3, 3}};
+  EXPECT_THAT(mat.transpose(), (matr2x3i{{1, 2, 3}, {1, 2, 3}}));
 }
 
 TEST(Matrix, AdditionSumsAllElements) {
   auto iden = identity2i();
-  auto result = matr2i({{{2, 0}, {0, 2}}});
+  auto result = matr2i{{2, 0}, {0, 2}};
   EXPECT_THAT(iden + iden, result);
 }
 
 TEST(Matrix, DefaultConstructorIsZeros) {
-  auto zero = matr2i({{{0, 0}, {0, 0}}});
+  auto zero = matr2i{{0, 0}, {0, 0}};
   EXPECT_THAT(matr2i{}, zero);
 }
 
@@ -81,23 +81,23 @@ TEST(Matrix, SubtractionDiffsAllElements) {
 }
 
 TEST(Matrix, NegationAltersAllElements) {
-  EXPECT_THAT(-identity2i(), matr2i({{{-1, 0}, {0, -1}}}));
+  EXPECT_THAT(-identity2i(), (matr2i{{-1, 0}, {0, -1}}));
 }
 
 TEST(Matrix, MultiplicationAltersAllElements) {
-  EXPECT_THAT(2 * identity2(), matr2({{{2.0, 0.0}, {0.0, 2.0}}}));
-  EXPECT_THAT(identity2() * 2, matr2({{{2.0, 0.0}, {0.0, 2.0}}}));
+  EXPECT_THAT(2 * identity2(), (matr2{{2.0, 0.0}, {0.0, 2.0}}));
+  EXPECT_THAT(identity2() * 2, (matr2{{2.0, 0.0}, {0.0, 2.0}}));
 }
 
 TEST(Matrix, DivisionAltersAllElements) {
-  EXPECT_THAT(identity2() / 2.0, matr2({{{0.5, 0.0}, {0.0, 0.5}}}));
+  EXPECT_THAT(identity2() / 2.0, (matr2{{0.5, 0.0}, {0.0, 0.5}}));
 }
 
 TEST(Matrix, MultiplyingSameSizeMatricesProducesSameSize) {
-  auto A = matr2i({{{1, 2}, {2, 3}}});
-  auto B = matr2i({{{1, 0}, {1, 1}}});
-  EXPECT_THAT(A * B, matr2i({{{3, 2}, {5, 3}}}));
-  EXPECT_THAT(B * A, matr2i({{{1, 2}, {3, 5}}}));
+  auto A = matr2i{{1, 2}, {2, 3}};
+  auto B = matr2i{{1, 0}, {1, 1}};
+  EXPECT_THAT(A * B, (matr2i{{3, 2}, {5, 3}}));
+  EXPECT_THAT(B * A, (matr2i{{1, 2}, {3, 5}}));
 }
 
 TEST(Matrix, MultiplyingDifferentSizeMatricesChangesSize) {
@@ -105,30 +105,31 @@ TEST(Matrix, MultiplyingDifferentSizeMatricesChangesSize) {
   using matr2x3i = math::matrix::matrix<int, 2, 3>;
   using matr3x2i = math::matrix::matrix<int, 3, 2>;
   using matr3x3i = math::matrix::matrix<int, 3, 3>;
-  auto A = matr3x2i({{{1, 0}, {0, 1}, {1, 1}}});
-  auto B = matr2x3i({{{0, 1, 0}, {1, 0, 1}}});
-  EXPECT_THAT(A * B, matr3x3i({{{0, 1, 0}, {1, 0, 1}, {1, 1, 1}}}));
-  EXPECT_THAT(B * A, matr2x2i({{{0, 1}, {2, 1}}}));
+  auto A = matr3x2i{{1, 0}, {0, 1}, {1, 1}};
+  auto B = matr2x3i{{0, 1, 0}, {1, 0, 1}};
+  EXPECT_THAT(A * B, (matr3x3i{{0, 1, 0}, {1, 0, 1}, {1, 1, 1}}));
+  EXPECT_THAT(B * A, (matr2x2i{{0, 1}, {2, 1}}));
 }
 
 TEST(Matrix, VectorMultiplicationProducesVector) {
   using vec2i = math::vector::vector<int, 2>;
-  auto A = matr2i({{{1, 0}, {1, 2}}});
-  auto x = vec2i({1, 2});
-  EXPECT_THAT(A * x, vec2i({1, 5}));
+  auto A = matr2i{{1, 0}, {1, 2}};
+  auto x = vec2i{1, 2};
+  EXPECT_THAT(A * x, vec2i(1, 5));
 }
 
 TEST(Matrix, ConcatenationExtends) {
   using namespace math::matrix;
-  auto A = matr2i({{{1, 2}, {2, 3}}});
-  auto B = matr2i({{{1, 0}, {1, 1}}});
+  using namespace math::vector;
+  auto A = matr2i{{1, 2}, {2, 3}};
+  auto B = matr2i{{1, 0}, {1, 1}};
   EXPECT_THAT(A.concat(B, concat_strategy::horizontal),
-              (matrix<int, 2, 4>({{{1, 2, 1, 0}, {2, 3, 1, 1}}})));
+              (matrix{vector{1, 2, 1, 0}, {2, 3, 1, 1}}));
   EXPECT_THAT(A.concat(B, concat_strategy::vertical),
-              (matrix<int, 4, 2>({{{1, 2}, {2, 3}, {1, 0}, {1, 1}}})));
-  EXPECT_THAT(A.concat(B, concat_strategy::diagonal),
-              (matrix<int, 4, 4>(
-                  {{{1, 2, 0, 0}, {2, 3, 0, 0}, {0, 0, 1, 0}, {0, 0, 1, 1}}})));
+              (matrix{vector{1, 2}, {2, 3}, {1, 0}, {1, 1}}));
+  EXPECT_THAT(
+      A.concat(B, concat_strategy::diagonal),
+      (matrix{vector{1, 2, 0, 0}, {2, 3, 0, 0}, {0, 0, 1, 0}, {0, 0, 1, 1}}));
 }
 
 // TEST(Matrix, Composition) {
@@ -136,17 +137,17 @@ TEST(Matrix, ConcatenationExtends) {
 //  using vec4 = math::vector::vector<double, 4>;
 //  using vec3 = math::vector::vector<double, 3>;
 //  auto rot = rotation<4>(math::degree{90}, rotate::X_AXIS);
-//  auto mov = translation(vec3({2.0, 2.5, 1.5}));
+//  auto mov = translation(vec3{2.0, 2.5, 1.5});
 //  auto scl = scalar(vec3(2.0, math::vector::fill));
 //  vec4 epsilon{0.00001, math::vector::fill};
-//  TS_ASSERT_DELTA((mov * scl * rot * vec4({1,2,3,1})),
-//                  (vec4({4.0,-1.5,-4.5,1.0})),
+//  TS_ASSERT_DELTA((mov * scl * rot * vec4{1,2,3,1}),
+//                  (vec4{4.0,-1.5,-4.5,1.0}),
 //                  epsilon);
 //}
 
 TEST(Matrix, MatrixConstructableFromVector) {
   using vec3 = math::vector::vector<double, 3>;
-  vec3 v = vec3({1, 2, 3});
+  vec3 v = vec3{1, 2, 3};
   math::matrix::matrix<double, 3, 1> m{v};
   EXPECT_THAT(m(0, 0), v[0]);
   EXPECT_THAT(m(1, 0), v[1]);
@@ -156,12 +157,14 @@ TEST(Matrix, MatrixConstructableFromVector) {
 TEST(Matrix, RowAllowsMutation) {
   matr2i A = identity2i();
   A[0][0] = 2;
-  EXPECT_THAT(A, matr2i({{{2, 0}, {0, 1}}}));
+  EXPECT_THAT(A, (matr2i{{2, 0}, {0, 1}}));
 }
 
 TEST(Matrix, CanAlterEntireRowInOneExpression) {
   matr2i A = identity2i();
   matr2i const B = 2 * A;
   A[0] = B[0];
-  EXPECT_THAT(A, matr2i({{{2, 0}, {0, 1}}}));
+  EXPECT_THAT(A, (matr2i{{2, 0}, {0, 1}}));
 }
+
+TEST(Matrix, CannotCompile) { matr2i{{2, 0}}; }