Repository: elalish/manifold (language: python)

Issue:
better error handling
e.g. poison the object that thrown an error, so we will not observe things in inconsistent states after throwing exceptions. This can happen when we have shared csg nodes, and got an error when evaluated for the first time, and inconsistent when trying to evaluate for the second time.


Discussion / hints:
I will work on this later this week.
Thinking about it, not sure if we need more than

```diff
diff --git a/src/manifold/src/boolean_result.cpp b/src/manifold/src/boolean_result.cpp
index ca5f4612..93631e2c 100644
--- a/src/manifold/src/boolean_result.cpp
+++ b/src/manifold/src/boolean_result.cpp
@@ -671,11 +671,22 @@ Manifold::Impl Boolean3::Result(OpType op) const {
   const int c3 = op == OpType::Intersect ? 1 : -1;
 
   if (inP_.IsEmpty()) {
+    if (inP_.status_ != Manifold::Error::NoError ||
+        inQ_.status_ != Manifold::Error::NoError) {
+      auto impl = Manifold::Impl();
+      impl.status_ = Manifold::Error::InvalidConstruction;
+      return impl;
+    }
     if (!inQ_.IsEmpty() && op == OpType::Add) {
       return inQ_;
     }
     return Manifold::Impl();
   } else if (inQ_.IsEmpty()) {
+    if (inQ_.status_ != Manifold::Error::NoError) {
+      auto impl = Manifold::Impl();
+      impl.status_ = Manifold::Error::InvalidConstruction;
+      return impl;
+    }
     if (op == OpType::Intersect) {
       return Manifold::Impl();
     }
```

This will propagate errors in the boolean operands.

Fix patch:
diff --git a/bindings/c/include/manifold/manifoldc.h b/bindings/c/include/manifold/manifoldc.h
index f8ab09ee4..0fd59721f 100644
--- a/bindings/c/include/manifold/manifoldc.h
+++ b/bindings/c/include/manifold/manifoldc.h
@@ -174,9 +174,9 @@ ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
 
 int manifold_is_empty(ManifoldManifold *m);
 ManifoldError manifold_status(ManifoldManifold *m);
-int manifold_num_vert(ManifoldManifold *m);
-int manifold_num_edge(ManifoldManifold *m);
-int manifold_num_tri(ManifoldManifold *m);
+size_t manifold_num_vert(ManifoldManifold *m);
+size_t manifold_num_edge(ManifoldManifold *m);
+size_t manifold_num_tri(ManifoldManifold *m);
 ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
 double manifold_precision(ManifoldManifold *m);
 int manifold_genus(ManifoldManifold *m);
diff --git a/bindings/c/manifoldc.cpp b/bindings/c/manifoldc.cpp
index 3f20eefc3..08db493e1 100644
--- a/bindings/c/manifoldc.cpp
+++ b/bindings/c/manifoldc.cpp
@@ -504,9 +504,9 @@ ManifoldError manifold_status(ManifoldManifold *m) {
   return to_c(error);
 }
 
-int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
-int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
-int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
+size_t manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
+size_t manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
+size_t manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
 int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
 
 ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
diff --git a/src/manifold/include/manifold/manifold.h b/src/manifold/include/manifold/manifold.h
index 15d78bb0c..9f04423d4 100644
--- a/src/manifold/include/manifold/manifold.h
+++ b/src/manifold/include/manifold/manifold.h
@@ -15,6 +15,7 @@
 #pragma once
 #include <functional>
 #include <memory>
+#include <optional>
 
 #include "manifold/common.h"
 #include "manifold/vec_view.h"
@@ -37,38 +38,28 @@ class CsgLeafNode;
  *  @{
  */
 
-template <typename Precision>
+template <typename Precision, typename I = uint32_t>
 struct MeshGLP {
   /// Number of property vertices
-  uint32_t NumVert() const {
-    ASSERT(vertProperties.size() / numProp <
-               static_cast<size_t>(std::numeric_limits<uint32_t>::max()),
-           std::out_of_range("mesh too large for MeshGL"));
-    return vertProperties.size() / numProp;
-  };
+  I NumVert() const { return vertProperties.size() / numProp; };
   /// Number of triangles
-  uint32_t NumTri() const {
-    ASSERT(triVerts.size() / 3 <
-               static_cast<size_t>(std::numeric_limits<uint32_t>::max()),
-           std::out_of_range("mesh too large for MeshGL"));
-    return triVerts.size() / 3;
-  };
+  I NumTri() const { return triVerts.size() / 3; };
   /// Number of properties per vertex, always >= 3.
-  uint32_t numProp = 3;
+  I numProp = 3;
   /// Flat, GL-style interleaved list of all vertex properties: propVal =
   /// vertProperties[vert * numProp + propIdx]. The first three properties are
   /// always the position x, y, z.
   std::vector<Precision> vertProperties;
   /// The vertex indices of the three triangle corners in CCW (from the outside)
   /// order, for each triangle.
-  std::vector<uint32_t> triVerts;
+  std::vector<I> triVerts;
   /// Optional: A list of only the vertex indicies that need to be merged to
   /// reconstruct the manifold.
-  std::vector<uint32_t> mergeFromVert;
+  std::vector<I> mergeFromVert;
   /// Optional: The same length as mergeFromVert, and the corresponding value
   /// contains the vertex to merge with. It will have an identical position, but
   /// the other properties may differ.
-  std::vector<uint32_t> mergeToVert;
+  std::vector<I> mergeToVert;
   /// Optional: Indicates runs of triangles that correspond to a particular
   /// input mesh instance. The runs encompass all of triVerts and are sorted
   /// by runOriginalID. Run i begins at triVerts[runIndex[i]] and ends at
@@ -76,7 +67,7 @@ struct MeshGLP {
   /// runIndex will always be 1 longer than runOriginalID, but same length is
   /// also allowed as input: triVerts.size() will be automatically appended in
   /// this case.
-  std::vector<uint32_t> runIndex;
+  std::vector<I> runIndex;
   /// Optional: The OriginalID of the mesh this triangle run came from. This ID
   /// is ideal for reapplying materials to the output mesh. Multiple runs may
   /// have the same ID, e.g. representing different copies of the same input
@@ -95,7 +86,7 @@ struct MeshGLP {
   /// supplying faceIDs, ensure that triangles with the same ID are in fact
   /// coplanar and have consistent properties (within some tolerance) or the
   /// output will be surprising.
-  std::vector<uint32_t> faceID;
+  std::vector<I> faceID;
   /// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If
   /// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed
   /// as 4 * (3 * tri + i) + j, i < 3, j < 4, representing the tangent value
@@ -117,9 +108,10 @@ struct MeshGLP {
                 vertProperties[offset + 2]);
   }
 
-  ivec3 GetTriVerts(size_t i) const {
+  glm::vec<3, I> GetTriVerts(size_t i) const {
     size_t offset = 3 * i;
-    return ivec3(triVerts[offset], triVerts[offset + 1], triVerts[offset + 2]);
+    return glm::vec<3, I>(triVerts[offset], triVerts[offset + 1],
+                          triVerts[offset + 2]);
   }
 };
 
@@ -130,7 +122,7 @@ struct MeshGLP {
  * store this missing information, allowing the manifold to be reconstructed.
  */
 using MeshGL = MeshGLP<float>;
-using MeshGL64 = MeshGLP<double>;
+using MeshGL64 = MeshGLP<double, size_t>;
 /** @} */
 
 /** @defgroup Core
@@ -231,11 +223,11 @@ class Manifold {
     InvalidConstruction,
   };
   Error Status() const;
-  int NumVert() const;
-  int NumEdge() const;
-  int NumTri() const;
-  int NumProp() const;
-  int NumPropVert() const;
+  size_t NumVert() const;
+  size_t NumEdge() const;
+  size_t NumTri() const;
+  size_t NumProp() const;
+  size_t NumPropVert() const;
   Box BoundingBox() const;
   double Precision() const;
   int Genus() const;
@@ -314,8 +306,8 @@ class Manifold {
    */
   ///@{
   bool MatchesTriNormals() const;
-  int NumDegenerateTris() const;
-  int NumOverlaps(const Manifold& second) const;
+  size_t NumDegenerateTris() const;
+  size_t NumOverlaps(const Manifold& second) const;
   ///@}
 
   struct Impl;
diff --git a/src/manifold/src/boolean_result.cpp b/src/manifold/src/boolean_result.cpp
index ca5f4612c..93631e2ca 100644
--- a/src/manifold/src/boolean_result.cpp
+++ b/src/manifold/src/boolean_result.cpp
@@ -671,11 +671,22 @@ Manifold::Impl Boolean3::Result(OpType op) const {
   const int c3 = op == OpType::Intersect ? 1 : -1;
 
   if (inP_.IsEmpty()) {
+    if (inP_.status_ != Manifold::Error::NoError ||
+        inQ_.status_ != Manifold::Error::NoError) {
+      auto impl = Manifold::Impl();
+      impl.status_ = Manifold::Error::InvalidConstruction;
+      return impl;
+    }
     if (!inQ_.IsEmpty() && op == OpType::Add) {
       return inQ_;
     }
     return Manifold::Impl();
   } else if (inQ_.IsEmpty()) {
+    if (inQ_.status_ != Manifold::Error::NoError) {
+      auto impl = Manifold::Impl();
+      impl.status_ = Manifold::Error::InvalidConstruction;
+      return impl;
+    }
     if (op == OpType::Intersect) {
       return Manifold::Impl();
     }
diff --git a/src/manifold/src/constructors.cpp b/src/manifold/src/constructors.cpp
index b4a8340c5..2479209b7 100644
--- a/src/manifold/src/constructors.cpp
+++ b/src/manifold/src/constructors.cpp
@@ -479,7 +479,7 @@ std::vector<Manifold> Manifold::Decompose() const {
     Vec<int> faceNew2Old(NumTri());
     const auto& halfedge = pImpl_->halfedge_;
     const int nFace =
-        copy_if(countAt(0), countAt(NumTri()), faceNew2Old.begin(),
+        copy_if(countAt(0_uz), countAt(NumTri()), faceNew2Old.begin(),
                 [i, &vertLabel, &halfedge](int face) {
                   return vertLabel[halfedge[3 * face].startVert] == i;
                 }) -
diff --git a/src/manifold/src/csg_tree.cpp b/src/manifold/src/csg_tree.cpp
index 825a6a14f..8f3a2f3b8 100644
--- a/src/manifold/src/csg_tree.cpp
+++ b/src/manifold/src/csg_tree.cpp
@@ -183,6 +183,11 @@ Manifold::Impl CsgLeafNode::Compose(
   std::vector<int> propVertIndices;
   int numPropOut = 0;
   for (auto &node : nodes) {
+    if (node->pImpl_->status_ != Manifold::Error::NoError) {
+      Manifold::Impl impl;
+      impl.status_ = Manifold::Error::InvalidConstruction;
+      return impl;
+    }
     double nodeOldScale = node->pImpl_->bBox_.Scale();
     double nodeNewScale =
         node->pImpl_->bBox_.Transform(node->transform_).Scale();
@@ -478,8 +483,6 @@ std::shared_ptr<Manifold::Impl> CsgOpNode::BatchBoolean(
           continue;
         }
         group.run([&, a, b]() {
-          const Manifold::Impl *aImpl;
-          const Manifold::Impl *bImpl;
           Boolean3 boolean(*getImplPtr(a), *getImplPtr(b), operation);
           queue.emplace(
               std::make_shared<Manifold::Impl>(boolean.Result(operation)));
@@ -507,11 +510,11 @@ std::shared_ptr<Manifold::Impl> CsgOpNode::BatchBoolean(
     results.pop_back();
     // boolean operation
     Boolean3 boolean(*a, *b, operation);
+    auto result = std::make_shared<Manifold::Impl>(boolean.Result(operation));
     if (results.size() == 0) {
-      return std::make_shared<Manifold::Impl>(boolean.Result(operation));
+      return result;
     }
-    results.push_back(
-        std::make_shared<const Manifold::Impl>(boolean.Result(operation)));
+    results.push_back(result);
     std::push_heap(results.begin(), results.end(), cmpFn);
   }
   return std::make_shared<Manifold::Impl>(*results.front());
diff --git a/src/manifold/src/impl.cpp b/src/manifold/src/impl.cpp
index b375b249b..225eae704 100644
--- a/src/manifold/src/impl.cpp
+++ b/src/manifold/src/impl.cpp
@@ -570,6 +570,10 @@ Manifold::Impl Manifold::Impl::Transform(const mat4x3& transform_) const {
   if (transform_ == mat4x3(1.0)) return *this;
   auto policy = autoPolicy(NumVert());
   Impl result;
+  if (status_ != Manifold::Error::NoError) {
+    result.status_ = status_;
+    return result;
+  }
   result.collider_ = collider_;
   result.meshRelation_ = meshRelation_;
   result.precision_ = precision_;
diff --git a/src/manifold/src/impl.h b/src/manifold/src/impl.h
index 7c82b1fa7..131321a96 100644
--- a/src/manifold/src/impl.h
+++ b/src/manifold/src/impl.h
@@ -65,8 +65,8 @@ struct Manifold::Impl {
   enum class Shape { Tetrahedron, Cube, Octahedron };
   Impl(Shape, const mat4x3 = mat4x3(1));
 
-  template <typename Precision>
-  Impl(const MeshGLP<Precision>& meshGL) {
+  template <typename Precision, typename I>
+  Impl(const MeshGLP<Precision, I>& meshGL) {
     const uint32_t numVert = meshGL.NumVert();
     const uint32_t numTri = meshGL.NumTri();
 
@@ -133,15 +133,15 @@ struct Manifold::Impl {
 
     Vec<TriRef> triRef;
     if (!meshGL.runOriginalID.empty()) {
-      std::vector<uint32_t> runIndex = meshGL.runIndex;
-      const uint32_t runEnd = meshGL.triVerts.size();
+      auto runIndex = meshGL.runIndex;
+      const auto runEnd = meshGL.triVerts.size();
       if (runIndex.empty()) {
-        runIndex = {0, runEnd};
+        runIndex = {0, static_cast<I>(runEnd)};
       } else if (runIndex.size() == meshGL.runOriginalID.size()) {
         runIndex.push_back(runEnd);
       }
       triRef.resize(meshGL.NumTri());
-      const int startID = Impl::ReserveIDs(meshGL.runOriginalID.size());
+      const auto startID = Impl::ReserveIDs(meshGL.runOriginalID.size());
       for (size_t i = 0; i < meshGL.runOriginalID.size(); ++i) {
         const int meshID = startID + i;
         const int originalID = meshGL.runOriginalID[i];
@@ -169,7 +169,7 @@ struct Manifold::Impl {
     for (size_t i = 0; i < numTri; ++i) {
       ivec3 tri;
       for (const size_t j : {0, 1, 2}) {
-        uint32_t vert = meshGL.triVerts[3 * i + j];
+        uint32_t vert = (uint32_t)meshGL.triVerts[3 * i + j];
         if (vert >= numVert) {
           MarkFailure(Error::VertexOutOfBounds);
           return;
@@ -183,8 +183,9 @@ struct Manifold::Impl {
         }
         if (numProp > 0) {
           meshRelation_.triProperties.push_back(
-              ivec3(meshGL.triVerts[3 * i], meshGL.triVerts[3 * i + 1],
-                    meshGL.triVerts[3 * i + 2]));
+              ivec3(static_cast<uint32_t>(meshGL.triVerts[3 * i]),
+                    static_cast<uint32_t>(meshGL.triVerts[3 * i + 1]),
+                    static_cast<uint32_t>(meshGL.triVerts[3 * i + 2])));
         }
       }
     }
diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp
index 452fffcdc..004af1112 100644
--- a/src/manifold/src/manifold.cpp
+++ b/src/manifold/src/manifold.cpp
@@ -57,9 +57,9 @@ Manifold Halfspace(Box bBox, vec3 normal, double originOffset) {
   return cutter.Rotate(0.0, yDeg, zDeg);
 }
 
-template <typename Precision>
-MeshGLP<Precision> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
-                                 ivec3 normalIdx) {
+template <typename Precision, typename I>
+MeshGLP<Precision, I> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
+                                    ivec3 normalIdx) {
   ZoneScoped;
   const int numProp = impl.NumProp();
   const int numVert = impl.NumPropVert();
@@ -69,7 +69,7 @@ MeshGLP<Precision> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
   const bool updateNormals =
       !isOriginal && glm::all(glm::greaterThan(normalIdx, ivec3(2)));
 
-  MeshGLP<Precision> out;
+  MeshGLP<Precision, I> out;
   out.precision =
       std::max(impl.precision_,
                std::numeric_limits<Precision>::epsilon() * impl.bBox_.Scale());
@@ -101,7 +101,7 @@ MeshGLP<Precision> GetMeshGLImpl(const manifold::Manifold::Impl& impl,
 
   std::vector<mat3> runNormalTransform;
   auto addRun = [updateNormals, isOriginal](
-                    MeshGLP<Precision>& out,
+                    MeshGLP<Precision, I>& out,
                     std::vector<mat3>& runNormalTransform, int tri,
                     const manifold::Manifold::Impl::Relation& rel) {
     out.runIndex.push_back(3 * tri);
@@ -352,7 +352,7 @@ Mesh Manifold::GetMesh() const {
  */
 MeshGL Manifold::GetMeshGL(ivec3 normalIdx) const {
   const Impl& impl = *GetCsgLeafNode().GetImpl();
-  return GetMeshGLImpl<float>(impl, normalIdx);
+  return GetMeshGLImpl<float, uint32_t>(impl, normalIdx);
 }
 
 /**
@@ -370,7 +370,7 @@ MeshGL Manifold::GetMeshGL(ivec3 normalIdx) const {
  */
 MeshGL64 Manifold::GetMeshGL64(ivec3 normalIdx) const {
   const Impl& impl = *GetCsgLeafNode().GetImpl();
-  return GetMeshGLImpl<double>(impl, normalIdx);
+  return GetMeshGLImpl<double, size_t>(impl, normalIdx);
 }
 
 /**
@@ -391,25 +391,31 @@ Manifold::Error Manifold::Status() const {
 /**
  * The number of vertices in the Manifold.
  */
-int Manifold::NumVert() const { return GetCsgLeafNode().GetImpl()->NumVert(); }
+size_t Manifold::NumVert() const {
+  return GetCsgLeafNode().GetImpl()->NumVert();
+}
 /**
  * The number of edges in the Manifold.
  */
-int Manifold::NumEdge() const { return GetCsgLeafNode().GetImpl()->NumEdge(); }
+size_t Manifold::NumEdge() const {
+  return GetCsgLeafNode().GetImpl()->NumEdge();
+}
 /**
  * The number of triangles in the Manifold.
  */
-int Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); }
+size_t Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); }
 /**
  * The number of properties per vertex in the Manifold.
  */
-int Manifold::NumProp() const { return GetCsgLeafNode().GetImpl()->NumProp(); }
+size_t Manifold::NumProp() const {
+  return GetCsgLeafNode().GetImpl()->NumProp();
+}
 /**
  * The number of property vertices in the Manifold. This will always be >=
  * NumVert, as some physical vertices may be duplicated to account for different
  * properties on different neighboring triangles.
  */
-int Manifold::NumPropVert() const {
+size_t Manifold::NumPropVert() const {
   return GetCsgLeafNode().GetImpl()->NumPropVert();
 }
 
@@ -471,7 +477,13 @@ int Manifold::OriginalID() const {
  */
 Manifold Manifold::AsOriginal(
     const std::vector<double>& propertyTolerance) const {
-  auto newImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
+  auto oldImpl = GetCsgLeafNode().GetImpl();
+  if (oldImpl->status_ != Error::NoError) {
+    auto newImpl = std::make_shared<Impl>();
+    newImpl->status_ = oldImpl->status_;
+    return Manifold(std::make_shared<CsgLeafNode>(newImpl));
+  }
+  auto newImpl = std::make_shared<Impl>(*oldImpl);
   newImpl->InitializeOriginal();
   newImpl->CreateFaces(propertyTolerance);
   newImpl->SimplifyTopology();
@@ -502,7 +514,7 @@ bool Manifold::MatchesTriNormals() const {
  * attempts to remove all of these, but it cannot always remove all of them
  * without changing the mesh by too much.
  */
-int Manifold::NumDegenerateTris() const {
+size_t Manifold::NumDegenerateTris() const {
   return GetCsgLeafNode().GetImpl()->NumDegenerateTris();
 }
 
@@ -512,7 +524,7 @@ int Manifold::NumDegenerateTris() const {
  *
  * @param other A Manifold to overlap with.
  */
-int Manifold::NumOverlaps(const Manifold& other) const {
+size_t Manifold::NumOverlaps(const Manifold& other) const {
   SparseIndices overlaps = GetCsgLeafNode().GetImpl()->EdgeCollisions(
       *other.GetCsgLeafNode().GetImpl());
   int num_overlaps = overlaps.size();
@@ -595,7 +607,13 @@ Manifold Manifold::Mirror(vec3 normal) const {
  * @param warpFunc A function that modifies a given vertex position.
  */
 Manifold Manifold::Warp(std::function<void(vec3&)> warpFunc) const {
-  auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
+  auto oldImpl = GetCsgLeafNode().GetImpl();
+  if (oldImpl->status_ != Error::NoError) {
+    auto pImpl = std::make_shared<Impl>();
+    pImpl->status_ = oldImpl->status_;
+    return Manifold(std::make_shared<CsgLeafNode>(pImpl));
+  }
+  auto pImpl = std::make_shared<Impl>(*oldImpl);
   pImpl->Warp(warpFunc);
   return Manifold(std::make_shared<CsgLeafNode>(pImpl));
 }
@@ -609,7 +627,13 @@ Manifold Manifold::Warp(std::function<void(vec3&)> warpFunc) const {
  */
 Manifold Manifold::WarpBatch(
     std::function<void(VecView<vec3>)> warpFunc) const {
-  auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
+  auto oldImpl = GetCsgLeafNode().GetImpl();
+  if (oldImpl->status_ != Error::NoError) {
+    auto pImpl = std::make_shared<Impl>();
+    pImpl->status_ = oldImpl->status_;
+    return Manifold(std::make_shared<CsgLeafNode>(pImpl));
+  }
+  auto pImpl = std::make_shared<Impl>(*oldImpl);
   pImpl->WarpBatch(warpFunc);
   return Manifold(std::make_shared<CsgLeafNode>(pImpl));
 }
diff --git a/src/manifold/src/sort.cpp b/src/manifold/src/sort.cpp
index 205ec47fd..6a1c5653b 100644
--- a/src/manifold/src/sort.cpp
+++ b/src/manifold/src/sort.cpp
@@ -87,8 +87,8 @@ struct ReindexFace {
   }
 };
 
-template <typename Precision>
-bool MergeMeshGLP(MeshGLP<Precision>& mesh) {
+template <typename Precision, typename I>
+bool MergeMeshGLP(MeshGLP<Precision, I>& mesh) {
   ZoneScoped;
   std::multiset<std::pair<int, int>> openEdges;
 
diff --git a/src/utilities/include/manifold/iters.h b/src/utilities/include/manifold/iters.h
index 8bdb026ea..e910acd89 100644
--- a/src/utilities/include/manifold/iters.h
+++ b/src/utilities/include/manifold/iters.h
@@ -120,7 +120,7 @@ struct CountingIterator {
   using value_type = T;
   using iterator_category = std::random_access_iterator_tag;
 
-  CountingIterator(T counter) : counter(counter) {}
+  constexpr CountingIterator(T counter) : counter(counter) {}
 
   value_type operator*() const { return counter; }
   value_type operator[](T i) const { return counter + i; }
@@ -190,8 +190,7 @@ struct CountingIterator {
   }
 };
 
-template <typename T>
-CountingIterator<T> countAt(T i) {
+constexpr CountingIterator<size_t> countAt(size_t i) {
   return CountingIterator(i);
 }
 


Test patch:
diff --git a/test/samples_test.cpp b/test/samples_test.cpp
index e682be81a..800c88ec0 100644
--- a/test/samples_test.cpp
+++ b/test/samples_test.cpp
@@ -87,7 +87,7 @@ TEST(Samples, Scallop) {
     ExportOptions options;
     const int numVert = scallop.NumVert();
     const int numHalfedge = 3 * scallop.NumTri();
-    for (int i = 0; i < scallop.NumVert(); ++i) {
+    for (size_t i = 0; i < scallop.NumVert(); ++i) {
       options.mat.vertColor.push_back({0, 0, 1, 1});
     }
     for (int i = 0; i < numHalfedge; ++i) {
diff --git a/test/smooth_test.cpp b/test/smooth_test.cpp
index efb7f48e8..a3e6206bd 100644
--- a/test/smooth_test.cpp
+++ b/test/smooth_test.cpp
@@ -239,7 +239,7 @@ TEST(Smooth, Csaszar) {
     options.mat.vertColor.resize(csaszar.NumVert());
     const vec4 blue(0, 0, 1, 1);
     const vec4 yellow(1, 1, 0, 1);
-    for (int tri = 0; tri < csaszar.NumTri(); ++tri) {
+    for (size_t tri = 0; tri < csaszar.NumTri(); ++tri) {
       for (int i : {0, 1, 2}) {
         const vec3& uvw = {0.5, 0.5, 0.0};
         const double alpha = std::min(uvw[0], std::min(uvw[1], uvw[2]));