Drop the runtime to C++11

This is necessary in order for us to be able to run on more platforms.

Diffs=
312a6c778 Drop the runtime to C++11
diff --git a/.rive_head b/.rive_head
index 100b2c9..18868aa 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-de4fe4d71a1eeef8e135991ca395c3159b42dc0d
+312a6c77883aaf469214e1dffc6953d4e82ace8c
diff --git a/build/premake5.lua b/build/premake5.lua
index 507d7c2..52390b9 100644
--- a/build/premake5.lua
+++ b/build/premake5.lua
@@ -40,7 +40,7 @@
 do
     kind 'StaticLib'
     language 'C++'
-    cppdialect 'C++17'
+    cppdialect 'C++11'
     toolset 'clang'
     targetdir '%{cfg.system}/bin/%{cfg.buildcfg}'
     objdir '%{cfg.system}/obj/%{cfg.buildcfg}'
diff --git a/dev/test/premake5.lua b/dev/test/premake5.lua
index aa95378..7e73360 100644
--- a/dev/test/premake5.lua
+++ b/dev/test/premake5.lua
@@ -23,7 +23,7 @@
 do
     kind 'ConsoleApp'
     language 'C++'
-    cppdialect 'C++17'
+    cppdialect 'C++11'
     toolset (_OPTIONS["toolset"] or "clang")
     targetdir 'build/bin/%{cfg.buildcfg}'
     objdir 'build/obj/%{cfg.buildcfg}'
diff --git a/include/rive/math/math_types.hpp b/include/rive/math/math_types.hpp
index 00b2bf0..1b6db5f 100644
--- a/include/rive/math/math_types.hpp
+++ b/include/rive/math/math_types.hpp
@@ -7,6 +7,7 @@
 
 #include "rive/rive_types.hpp"
 #include <cmath>
+#include <limits>
 #include <string.h>
 
 namespace rive
@@ -17,13 +18,13 @@
 constexpr float PI = 3.14159265f;
 constexpr float EPSILON = 1.f / (1 << 12); // Common threshold for detecting values near zero.
 
-[[maybe_unused]] inline bool nearly_zero(float a, float tolerance = EPSILON)
+RIVE_MAYBE_UNUSED inline bool nearly_zero(float a, float tolerance = EPSILON)
 {
     assert(tolerance >= 0);
     return fabsf(a) <= tolerance;
 }
 
-[[maybe_unused]] inline bool nearly_equal(float a, float b, float tolerance = EPSILON)
+RIVE_MAYBE_UNUSED inline bool nearly_equal(float a, float b, float tolerance = EPSILON)
 {
     return nearly_zero(b - a, tolerance);
 }
@@ -38,7 +39,7 @@
 //
 // Reference:
 // https://stackoverflow.com/questions/42926763/the-behaviour-of-floating-point-division-by-zero
-[[maybe_unused]]
+RIVE_MAYBE_UNUSED
 #if defined(__clang__) || defined(__GNUC__)
 __attribute__((no_sanitize("float-divide-by-zero"), always_inline))
 #endif
@@ -46,16 +47,16 @@
 ieee_float_divide(float a, float b)
 {
     static_assert(std::numeric_limits<float>::is_iec559,
-                  "Conformant IEEE 754 behavior for NaN and Inf is required.");
+                  "conformant IEEE 754 behavior for NaN and Inf is required");
     return a / b;
 }
 
 // Reinterprets the underlying bits of src as the given type.
 template <typename Dst, typename Src> Dst bit_cast(const Src& src)
 {
-    static_assert(sizeof(Dst) == sizeof(Src));
+    static_assert(sizeof(Dst) == sizeof(Src), "sizes of both types must match");
     Dst dst;
-    memcpy(&dst, &src, sizeof(Dst));
+    RIVE_INLINE_MEMCPY(&dst, &src, sizeof(Dst));
     return dst;
 }
 } // namespace math
diff --git a/include/rive/math/raw_path.hpp b/include/rive/math/raw_path.hpp
index ee6affc..f441917 100644
--- a/include/rive/math/raw_path.hpp
+++ b/include/rive/math/raw_path.hpp
@@ -14,6 +14,7 @@
 #include <cmath>
 #include <stdio.h>
 #include <cstdint>
+#include <tuple>
 #include <vector>
 
 namespace rive
@@ -108,7 +109,7 @@
 
     private:
         // How much should we advance pts after encountering this verb?
-        constexpr static int PtsAdvanceAfterVerb(PathVerb verb)
+        inline static int PtsAdvanceAfterVerb(PathVerb verb)
         {
             switch (verb)
             {
@@ -123,14 +124,14 @@
                 case PathVerb::close:
                     return 0;
             }
-            RIVE_UNREACHABLE;
+            RIVE_UNREACHABLE();
         }
 
         // Where is p0 relative to our m_pts pointer? We find the start point of segments by
         // peeking backwards from the current point, which works as long as there is always a
         // PathVerb::move before any geometry. (injectImplicitMoveToIfNeeded() guarantees this
         // to be the case.)
-        constexpr static int PtsBacksetForVerb(PathVerb verb)
+        inline static int PtsBacksetForVerb(PathVerb verb)
         {
             switch (verb)
             {
@@ -145,7 +146,7 @@
                 case PathVerb::close:
                     return -1;
             }
-            RIVE_UNREACHABLE;
+            RIVE_UNREACHABLE();
         }
 
         const PathVerb* m_verbs;
@@ -158,8 +159,10 @@
     {
         RawPath dst;
         // todo: dst.reserve(src.ptCount, src.verbCount);
-        for (auto [verb, pts] : *this)
+        for (auto iter : *this)
         {
+            PathVerb verb = std::get<0>(iter);
+            const Vec2D* pts = std::get<1>(iter);
             switch (verb)
             {
                 case PathVerb::move:
diff --git a/include/rive/math/simd.hpp b/include/rive/math/simd.hpp
index 7ef17e0..9cb90aa 100644
--- a/include/rive/math/simd.hpp
+++ b/include/rive/math/simd.hpp
@@ -14,6 +14,7 @@
 #ifndef _RIVE_SIMD_HPP_
 #define _RIVE_SIMD_HPP_
 
+#include "rive/rive_types.hpp"
 #include <cassert>
 #include <limits>
 #include <stdint.h>
@@ -35,13 +36,6 @@
 
 #if defined(__clang__) || defined(__GNUC__)
 
-#define SIMD_ALWAYS_INLINE inline __attribute__((always_inline))
-
-// Recommended in https://clang.llvm.org/docs/LanguageExtensions.html#feature-checking-macros
-#ifndef __has_builtin
-#define __has_builtin(x) 0
-#endif
-
 namespace rive
 {
 namespace simd
@@ -55,21 +49,12 @@
 
 #else
 
-#define SIMD_ALWAYS_INLINE inline
-#define __has_builtin(x) 0
-
 // gvec needs to be polyfilled with templates.
 #pragma message("performance: ext_vector_type not supported. Consider using clang.")
 #include "simd_gvec_polyfill.hpp"
 
 #endif
 
-#if __has_builtin(__builtin_memcpy)
-#define SIMD_INLINE_MEMCPY __builtin_memcpy
-#else
-#define SIMD_INLINE_MEMCPY memcpy
-#endif
-
 namespace rive
 {
 namespace simd
@@ -81,7 +66,7 @@
 //
 
 // Returns true if all elements in x are equal to 0.
-template <int N> SIMD_ALWAYS_INLINE bool any(gvec<int32_t, N> x)
+template <int N> RIVE_ALWAYS_INLINE bool any(gvec<int32_t, N> x)
 {
 #if __has_builtin(__builtin_reduce_or)
     return __builtin_reduce_or(x);
@@ -98,7 +83,7 @@
 }
 
 // Returns true if all elements in x are equal to ~0.
-template <int N> SIMD_ALWAYS_INLINE bool all(gvec<int32_t, N> x)
+template <int N> RIVE_ALWAYS_INLINE bool all(gvec<int32_t, N> x)
 {
 #if __has_builtin(__builtin_reduce_and)
     return __builtin_reduce_and(x);
@@ -112,7 +97,7 @@
 template <typename T,
           int N,
           typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
-SIMD_ALWAYS_INLINE gvec<int32_t, N> isnan(gvec<T, N> x)
+RIVE_ALWAYS_INLINE gvec<int32_t, N> isnan(gvec<T, N> x)
 {
     return ~(x == x);
 }
@@ -127,7 +112,7 @@
 
 // Elementwise ternary expression: "_if ? _then : _else" for each component.
 template <typename T, int N>
-SIMD_ALWAYS_INLINE gvec<T, N> if_then_else(gvec<int32_t, N> _if, gvec<T, N> _then, gvec<T, N> _else)
+RIVE_ALWAYS_INLINE gvec<T, N> if_then_else(gvec<int32_t, N> _if, gvec<T, N> _then, gvec<T, N> _else)
 {
 #if defined(__clang_major__) && __clang_major__ >= 13
     // The '?:' operator supports a vector condition beginning in clang 13.
@@ -143,7 +128,7 @@
 
 // Similar to std::min(), with a noteworthy difference:
 // If a[i] or b[i] is NaN and the other is not, returns whichever is _not_ NaN.
-template <typename T, int N> SIMD_ALWAYS_INLINE gvec<T, N> min(gvec<T, N> a, gvec<T, N> b)
+template <typename T, int N> RIVE_ALWAYS_INLINE gvec<T, N> min(gvec<T, N> a, gvec<T, N> b)
 {
 #if __has_builtin(__builtin_elementwise_min)
     return __builtin_elementwise_min(a, b);
@@ -156,7 +141,7 @@
 
 // Similar to std::max(), with a noteworthy difference:
 // If a[i] or b[i] is NaN and the other is not, returns whichever is _not_ NaN.
-template <typename T, int N> SIMD_ALWAYS_INLINE gvec<T, N> max(gvec<T, N> a, gvec<T, N> b)
+template <typename T, int N> RIVE_ALWAYS_INLINE gvec<T, N> max(gvec<T, N> a, gvec<T, N> b)
 {
 #if __has_builtin(__builtin_elementwise_max)
     return __builtin_elementwise_max(a, b);
@@ -174,14 +159,14 @@
 //   Ignores hi and/or lo if they are NaN.
 //
 template <typename T, int N>
-SIMD_ALWAYS_INLINE gvec<T, N> clamp(gvec<T, N> x, gvec<T, N> lo, gvec<T, N> hi)
+RIVE_ALWAYS_INLINE gvec<T, N> clamp(gvec<T, N> x, gvec<T, N> lo, gvec<T, N> hi)
 {
     return min(max(lo, x), hi);
 }
 
 // Returns the absolute value of x per element, with one exception:
 // If x[i] is an integer type and equal to the minimum representable value, returns x[i].
-template <typename T, int N> SIMD_ALWAYS_INLINE gvec<T, N> abs(gvec<T, N> x)
+template <typename T, int N> RIVE_ALWAYS_INLINE gvec<T, N> abs(gvec<T, N> x)
 {
 #if __has_builtin(__builtin_elementwise_abs)
     return __builtin_elementwise_abs(x);
@@ -193,7 +178,7 @@
 
 ////// Floating Point Functions //////
 
-template <int N> SIMD_ALWAYS_INLINE gvec<float, N> floor(gvec<float, N> x)
+template <int N> RIVE_ALWAYS_INLINE gvec<float, N> floor(gvec<float, N> x)
 {
 #if __has_builtin(__builtin_elementwise_floor)
     return __builtin_elementwise_floor(x);
@@ -206,7 +191,7 @@
 #endif
 }
 
-template <int N> SIMD_ALWAYS_INLINE gvec<float, N> ceil(gvec<float, N> x)
+template <int N> RIVE_ALWAYS_INLINE gvec<float, N> ceil(gvec<float, N> x)
 {
 #if __has_builtin(__builtin_elementwise_ceil)
     return __builtin_elementwise_ceil(x);
@@ -219,7 +204,7 @@
 }
 
 // IEEE compliant sqrt.
-template <int N> SIMD_ALWAYS_INLINE gvec<float, N> sqrt(gvec<float, N> x)
+template <int N> RIVE_ALWAYS_INLINE gvec<float, N> sqrt(gvec<float, N> x)
 {
     // There isn't an elementwise builtin for sqrt. We define architecture-specific specializations
     // of this function later.
@@ -229,42 +214,42 @@
 }
 
 #ifdef __SSE__
-template <> SIMD_ALWAYS_INLINE gvec<float, 4> sqrt(gvec<float, 4> x)
+template <> RIVE_ALWAYS_INLINE gvec<float, 4> sqrt(gvec<float, 4> x)
 {
     __m128 _x;
-    SIMD_INLINE_MEMCPY(&_x, &x, sizeof(float) * 4);
+    RIVE_INLINE_MEMCPY(&_x, &x, sizeof(float) * 4);
     _x = _mm_sqrt_ps(_x);
-    SIMD_INLINE_MEMCPY(&x, &_x, sizeof(float) * 4);
+    RIVE_INLINE_MEMCPY(&x, &_x, sizeof(float) * 4);
     return x;
 }
 
-template <> SIMD_ALWAYS_INLINE gvec<float, 2> sqrt(gvec<float, 2> x)
+template <> RIVE_ALWAYS_INLINE gvec<float, 2> sqrt(gvec<float, 2> x)
 {
     __m128 _x;
-    SIMD_INLINE_MEMCPY(&_x, &x, sizeof(float) * 2);
+    RIVE_INLINE_MEMCPY(&_x, &x, sizeof(float) * 2);
     _x = _mm_sqrt_ps(_x);
-    SIMD_INLINE_MEMCPY(&x, &_x, sizeof(float) * 2);
+    RIVE_INLINE_MEMCPY(&x, &_x, sizeof(float) * 2);
     return x;
 }
 #endif
 
 #ifdef __ARM_NEON__
 #ifdef __aarch64__
-template <> SIMD_ALWAYS_INLINE gvec<float, 4> sqrt(gvec<float, 4> x)
+template <> RIVE_ALWAYS_INLINE gvec<float, 4> sqrt(gvec<float, 4> x)
 {
     float32x4_t _x;
-    SIMD_INLINE_MEMCPY(&_x, &x, sizeof(float) * 4);
+    RIVE_INLINE_MEMCPY(&_x, &x, sizeof(float) * 4);
     _x = vsqrtq_f32(_x);
-    SIMD_INLINE_MEMCPY(&x, &_x, sizeof(float) * 4);
+    RIVE_INLINE_MEMCPY(&x, &_x, sizeof(float) * 4);
     return x;
 }
 
-template <> SIMD_ALWAYS_INLINE gvec<float, 2> sqrt(gvec<float, 2> x)
+template <> RIVE_ALWAYS_INLINE gvec<float, 2> sqrt(gvec<float, 2> x)
 {
     float32x2_t _x;
-    SIMD_INLINE_MEMCPY(&_x, &x, sizeof(float) * 2);
+    RIVE_INLINE_MEMCPY(&_x, &x, sizeof(float) * 2);
     _x = vsqrt_f32(_x);
-    SIMD_INLINE_MEMCPY(&x, &_x, sizeof(float) * 2);
+    RIVE_INLINE_MEMCPY(&x, &_x, sizeof(float) * 2);
     return x;
 }
 #endif
@@ -272,12 +257,12 @@
 
 // This will only be present when building with Emscripten and "-msimd128".
 #if __has_builtin(__builtin_wasm_sqrt_f32x4)
-template <> SIMD_ALWAYS_INLINE gvec<float, 4> sqrt(gvec<float, 4> x)
+template <> RIVE_ALWAYS_INLINE gvec<float, 4> sqrt(gvec<float, 4> x)
 {
     return __builtin_wasm_sqrt_f32x4(x);
 }
 
-template <> SIMD_ALWAYS_INLINE gvec<float, 2> sqrt(gvec<float, 2> x)
+template <> RIVE_ALWAYS_INLINE gvec<float, 2> sqrt(gvec<float, 2> x)
 {
     gvec<float, 4> _x{x.x, x.y};
     _x = __builtin_wasm_sqrt_f32x4(_x);
@@ -291,7 +276,7 @@
 //
 // See: https://stackoverflow.com/a/36387954
 #define SIMD_FAST_ACOS_MAX_ERROR 0.0167552f // .96 degrees
-template <int N> SIMD_ALWAYS_INLINE gvec<float, N> fast_acos(gvec<float, N> x)
+template <int N> RIVE_ALWAYS_INLINE gvec<float, N> fast_acos(gvec<float, N> x)
 {
     constexpr static float a = -0.939115566365855f;
     constexpr static float b = 0.9217841528914573f;
@@ -306,36 +291,36 @@
 
 ////// Loading and storing //////
 
-template <typename T, int N> SIMD_ALWAYS_INLINE gvec<T, N> load(const void* ptr)
+template <typename T, int N> RIVE_ALWAYS_INLINE gvec<T, N> load(const void* ptr)
 {
     gvec<T, N> ret;
-    SIMD_INLINE_MEMCPY(&ret, ptr, sizeof(T) * N);
+    RIVE_INLINE_MEMCPY(&ret, ptr, sizeof(T) * N);
     return ret;
 }
-SIMD_ALWAYS_INLINE gvec<float, 2> load2f(const void* ptr) { return load<float, 2>(ptr); }
-SIMD_ALWAYS_INLINE gvec<float, 4> load4f(const void* ptr) { return load<float, 4>(ptr); }
-SIMD_ALWAYS_INLINE gvec<int32_t, 2> load2i(const void* ptr) { return load<int32_t, 2>(ptr); }
-SIMD_ALWAYS_INLINE gvec<int32_t, 4> load4i(const void* ptr) { return load<int32_t, 4>(ptr); }
-SIMD_ALWAYS_INLINE gvec<uint32_t, 2> load2ui(const void* ptr) { return load<uint32_t, 2>(ptr); }
-SIMD_ALWAYS_INLINE gvec<uint32_t, 4> load4ui(const void* ptr) { return load<uint32_t, 4>(ptr); }
+RIVE_ALWAYS_INLINE gvec<float, 2> load2f(const void* ptr) { return load<float, 2>(ptr); }
+RIVE_ALWAYS_INLINE gvec<float, 4> load4f(const void* ptr) { return load<float, 4>(ptr); }
+RIVE_ALWAYS_INLINE gvec<int32_t, 2> load2i(const void* ptr) { return load<int32_t, 2>(ptr); }
+RIVE_ALWAYS_INLINE gvec<int32_t, 4> load4i(const void* ptr) { return load<int32_t, 4>(ptr); }
+RIVE_ALWAYS_INLINE gvec<uint32_t, 2> load2ui(const void* ptr) { return load<uint32_t, 2>(ptr); }
+RIVE_ALWAYS_INLINE gvec<uint32_t, 4> load4ui(const void* ptr) { return load<uint32_t, 4>(ptr); }
 
-template <typename T, int N> SIMD_ALWAYS_INLINE void store(void* dst, gvec<T, N> vec)
+template <typename T, int N> RIVE_ALWAYS_INLINE void store(void* dst, gvec<T, N> vec)
 {
-    SIMD_INLINE_MEMCPY(dst, &vec, sizeof(T) * N);
+    RIVE_INLINE_MEMCPY(dst, &vec, sizeof(T) * N);
 }
 
 template <typename T, int M, int N>
-SIMD_ALWAYS_INLINE gvec<T, M + N> join(gvec<T, M> a, gvec<T, N> b)
+RIVE_ALWAYS_INLINE gvec<T, M + N> join(gvec<T, M> a, gvec<T, N> b)
 {
     T data[M + N];
-    SIMD_INLINE_MEMCPY(data, &a, sizeof(T) * M);
-    SIMD_INLINE_MEMCPY(data + M, &b, sizeof(T) * N);
+    RIVE_INLINE_MEMCPY(data, &a, sizeof(T) * M);
+    RIVE_INLINE_MEMCPY(data + M, &b, sizeof(T) * N);
     return load<T, M + N>(data);
 }
 
 ////// Basic linear algebra //////
 
-template <typename T, int N> SIMD_ALWAYS_INLINE T dot(gvec<T, N> a, gvec<T, N> b)
+template <typename T, int N> RIVE_ALWAYS_INLINE T dot(gvec<T, N> a, gvec<T, N> b)
 {
     auto d = a * b;
     T s = d[0];
@@ -346,20 +331,20 @@
 
 // We can use __builtin_reduce_add for integer types.
 #if __has_builtin(__builtin_reduce_add)
-template <int N> SIMD_ALWAYS_INLINE int32_t dot(gvec<int32_t, N> a, gvec<int32_t, N> b)
+template <int N> RIVE_ALWAYS_INLINE int32_t dot(gvec<int32_t, N> a, gvec<int32_t, N> b)
 {
     auto d = a * b;
     return __builtin_reduce_add(d);
 }
 
-template <int N> SIMD_ALWAYS_INLINE uint32_t dot(gvec<uint32_t, N> a, gvec<uint32_t, N> b)
+template <int N> RIVE_ALWAYS_INLINE uint32_t dot(gvec<uint32_t, N> a, gvec<uint32_t, N> b)
 {
     auto d = a * b;
     return __builtin_reduce_add(d);
 }
 #endif
 
-SIMD_ALWAYS_INLINE float cross(gvec<float, 2> a, gvec<float, 2> b)
+RIVE_ALWAYS_INLINE float cross(gvec<float, 2> a, gvec<float, 2> b)
 {
     auto c = a * b.yx;
     return c.x - c.y;
@@ -373,7 +358,7 @@
 // structure seems to get better precision for things like chopping cubics on exact cusp points than
 // "a*(1 - t) + b*t" (which would return exactly b when t == 1).
 template <int N>
-SIMD_ALWAYS_INLINE gvec<float, N> mix(gvec<float, N> a, gvec<float, N> b, gvec<float, N> t)
+RIVE_ALWAYS_INLINE gvec<float, N> mix(gvec<float, N> a, gvec<float, N> b, gvec<float, N> t)
 {
     assert(simd::all(0.f <= t && t < 1.f));
     return (b - a) * t + a;
@@ -381,8 +366,7 @@
 } // namespace simd
 } // namespace rive
 
-#undef SIMD_ALWAYS_INLINE
-#undef SIMD_INLINE_MEMCPY
+#undef RIVE_INLINE_MEMCPY
 
 namespace rive
 {
diff --git a/include/rive/math/simd_gvec_polyfill.hpp b/include/rive/math/simd_gvec_polyfill.hpp
index f1ab72b..a590aab 100644
--- a/include/rive/math/simd_gvec_polyfill.hpp
+++ b/include/rive/math/simd_gvec_polyfill.hpp
@@ -143,10 +143,9 @@
     T& operator[](size_t i) { return gvec_data<T, N>::data[i]; }
 };
 
-static_assert(sizeof(gvec<float, 1>) == 4);
-static_assert(sizeof(gvec<float, 2>) == 8);
-static_assert(sizeof(gvec<float, 3>) == 12);
-static_assert(sizeof(gvec<float, 4>) == 16);
+static_assert(sizeof(gvec<float, 1>) == 4, "gvec<1> is expected to be tightly packed");
+static_assert(sizeof(gvec<float, 2>) == 8, "gvec<2> is expected to be tightly packed");
+static_assert(sizeof(gvec<float, 4>) == 16, "gvec<4> is expected to be tightly packed");
 
 #define DECL_UNARY_OP(_OP_)                                                                        \
     template <typename T, int N, Swizzle Z> gvec<T, N> operator _OP_(gvec<T, N, Z> x)              \
diff --git a/include/rive/rive_types.hpp b/include/rive/rive_types.hpp
index 13fb53b..3450d76 100644
--- a/include/rive/rive_types.hpp
+++ b/include/rive/rive_types.hpp
@@ -9,44 +9,45 @@
 #ifndef _RIVE_TYPES_HPP_
 #define _RIVE_TYPES_HPP_
 
-// clang-format off
+#include <memory>   // For unique_ptr.
+#include <string.h> // For memcpy.
 
 #if defined(DEBUG) && defined(NDEBUG)
-    #error "can't determine if we're debug or release"
+#error "can't determine if we're debug or release"
 #endif
 
 #if !defined(DEBUG) && !defined(NDEBUG)
-    // we have to make a decision what mode we're in
-    // historically this has been to look for NDEBUG, and in its
-    // absence assume we're DEBUG.
-    #define DEBUG  1
-    // fyi - Xcode seems to set DEBUG (or not), so the above guess
-    // doesn't work for them - so our projects may need to explicitly
-    // set NDEBUG in our 'release' builds.
+// we have to make a decision what mode we're in
+// historically this has been to look for NDEBUG, and in its
+// absence assume we're DEBUG.
+#define DEBUG 1
+// fyi - Xcode seems to set DEBUG (or not), so the above guess
+// doesn't work for them - so our projects may need to explicitly
+// set NDEBUG in our 'release' builds.
 #endif
 
 #ifdef NDEBUG
-    #ifndef RELEASE
-        #define RELEASE 1
-    #endif
-#else   // debug mode
-    #ifndef DEBUG
-        #define DEBUG   1
-    #endif
+#ifndef RELEASE
+#define RELEASE 1
+#endif
+#else // debug mode
+#ifndef DEBUG
+#define DEBUG 1
+#endif
 #endif
 
 // Some checks to guess what platform we're building for
 
 #ifdef __APPLE__
 
-    #define RIVE_BUILD_FOR_APPLE
-    #include <TargetConditionals.h>
+#define RIVE_BUILD_FOR_APPLE
+#include <TargetConditionals.h>
 
-    #if TARGET_OS_IPHONE
-        #define RIVE_BUILD_FOR_IOS
-    #elif TARGET_OS_MAC
-        #define RIVE_BUILD_FOR_OSX
-    #endif
+#if TARGET_OS_IPHONE
+#define RIVE_BUILD_FOR_IOS
+#elif TARGET_OS_MAC
+#define RIVE_BUILD_FOR_OSX
+#endif
 
 #endif
 
@@ -60,14 +61,59 @@
 #include <type_traits>
 
 // Annotations to assert unreachable control flow.
-#ifdef __GNUC__ // GCC 4.8+, Clang, Intel and others compatible with GCC (-std=c++0x or above)
-    #define RIVE_UNREACHABLE __builtin_unreachable()
+#if defined(__GNUC__) || defined(__clang__)
+#define RIVE_UNREACHABLE __builtin_unreachable
 #elif _MSC_VER
-    #define RIVE_UNREACHABLE __assume(0)
+#define RIVE_UNREACHABLE() __assume(0)
 #else
-    #define RIVE_UNREACHABLE do {} while(0)
+#define RIVE_UNREACHABLE()                                                                         \
+    do                                                                                             \
+    {                                                                                              \
+    } while (0)
 #endif
 
-// clang-format on
+#if __cplusplus >= 201703L
+#define RIVE_MAYBE_UNUSED [[maybe_unused]]
+#else
+#define RIVE_MAYBE_UNUSED
+#endif
+
+#if __cplusplus >= 201703L
+#define RIVE_FALLTHROUGH [[fallthrough]]
+#elif defined(__clang__)
+#define RIVE_FALLTHROUGH [[clang::fallthrough]]
+#else
+#define RIVE_FALLTHROUGH
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define RIVE_ALWAYS_INLINE inline __attribute__((always_inline))
+#else
+#define RIVE_ALWAYS_INLINE inline
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+// Recommended in https://clang.llvm.org/docs/LanguageExtensions.html#feature-checking-macros
+#ifndef __has_builtin
+#define __has_builtin(x) 0
+#endif
+#else
+#define __has_builtin(x) 0
+#endif
+
+#if __has_builtin(__builtin_memcpy)
+#define RIVE_INLINE_MEMCPY __builtin_memcpy
+#else
+#define RIVE_INLINE_MEMCPY memcpy
+#endif
+
+// Backports of later stl functions.
+namespace rivestd
+{
+template <class T, class... Args> std::unique_ptr<T> make_unique(Args&&... args)
+{
+    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+} // namespace rivestd
 
 #endif // rive_types
diff --git a/include/rive/simple_array.hpp b/include/rive/simple_array.hpp
index db2ccaf..c295abe 100644
--- a/include/rive/simple_array.hpp
+++ b/include/rive/simple_array.hpp
@@ -27,6 +27,41 @@
 } // namespace SimpleArrayTesting
 #endif
 
+// Helper for constructing and destructing arrays of objects.
+template <typename T, bool IsPOD = std::is_pod<T>()> class SimpleArrayHelper
+{
+public:
+    static_assert(!std::is_pod<T>(), "This helper is for non-POD types.");
+    static void DefaultConstructArray(T* ptr, T* end)
+    {
+        for (; ptr < end; ++ptr)
+            new (ptr) T();
+    }
+    static void CopyConstructArray(const T* first, const T* end, T* ptr)
+    {
+        for (; first < end; ++first, ++ptr)
+            new (ptr) T(*first);
+    }
+    static void DestructArray(T* ptr, T* end)
+    {
+        for (; ptr < end; ++ptr)
+            ptr->~T();
+    }
+};
+
+// Specialized helper for constructing and destructing arrays of POD objects.
+template <typename T> class SimpleArrayHelper<T, true>
+{
+public:
+    static_assert(std::is_pod<T>(), "This helper is only for POD types.");
+    static void DefaultConstructArray(T* ptr, T* end) {}
+    static void CopyConstructArray(const T* first, const T* end, T* ptr)
+    {
+        memcpy(ptr, first, reinterpret_cast<uintptr_t>(end) - reinterpret_cast<uintptr_t>(first));
+    }
+    static void DestructArray(T* ptr, T* end) {}
+};
+
 /// Lightweight heap array meant to be used when knowing the exact memory layout
 /// of the simple array is necessary, like marshaling the data to Dart/C#/Wasm.
 /// Note that it intentionally doesn't have push/add/resize functionality as
@@ -41,14 +76,7 @@
     SimpleArray() : m_ptr(nullptr), m_size(0) {}
     SimpleArray(size_t size) : m_ptr(static_cast<T*>(malloc(size * sizeof(T)))), m_size(size)
     {
-        if constexpr (!std::is_pod<T>())
-        {
-            for (T *element = m_ptr, *end = m_ptr + m_size; element < end; element++)
-            {
-                new (element) T();
-            }
-        }
-
+        SimpleArrayHelper<T>::DefaultConstructArray(m_ptr, m_ptr + m_size);
 #ifdef TESTING
         SimpleArrayTesting::mallocCount++;
 #endif
@@ -56,28 +84,18 @@
     SimpleArray(const T* ptr, size_t size) : SimpleArray(size)
     {
         assert(ptr <= ptr + size);
-        if constexpr (std::is_pod<T>())
-        {
-            memcpy(m_ptr, ptr, size * sizeof(T));
-        }
-        else
-        {
-            for (T *element = m_ptr, *end = m_ptr + m_size; element < end; element++)
-            {
-                new (element) T(ptr++);
-            }
-        }
+        SimpleArrayHelper<T>::CopyConstructArray(ptr, ptr + size, m_ptr);
     }
 
     constexpr SimpleArray(const SimpleArray<T>& other) : SimpleArray(other.m_ptr, other.m_size) {}
 
-    constexpr SimpleArray(SimpleArray<T>&& other) : m_ptr(other.m_ptr), m_size(other.m_size)
+    SimpleArray(SimpleArray<T>&& other) : m_ptr(other.m_ptr), m_size(other.m_size)
     {
         other.m_ptr = nullptr;
         other.m_size = 0;
     }
 
-    constexpr SimpleArray(SimpleArrayBuilder<T>&& other);
+    SimpleArray(SimpleArrayBuilder<T>&& other);
 
     SimpleArray<T>& operator=(const SimpleArray<T>& other) = delete;
 
@@ -93,30 +111,20 @@
     SimpleArray<T>& operator=(SimpleArrayBuilder<T>&& other);
 
     template <typename Container>
-    constexpr SimpleArray(Container& c) : SimpleArray(std::data(c), std::size(c))
+    constexpr SimpleArray(Container& c) : SimpleArray(c.data(), c.size())
     {}
-    constexpr SimpleArray(std::initializer_list<T> il) : SimpleArray(std::data(il), std::size(il))
+    constexpr SimpleArray(const std::initializer_list<T>& il) : SimpleArray(il.begin(), il.size())
     {}
     ~SimpleArray()
     {
-        if constexpr (!std::is_pod<T>())
-        {
-            for (T *element = m_ptr, *end = m_ptr + m_size; element < end; element++)
-            {
-                element->~T();
-            }
-        }
+        SimpleArrayHelper<T>::DestructArray(m_ptr, m_ptr + m_size);
         free(m_ptr);
 #ifdef TESTING
         SimpleArrayTesting::freeCount++;
 #endif
     }
 
-    constexpr T& operator[](size_t index) const
-    {
-        assert(index < m_size);
-        return m_ptr[index];
-    }
+    T& operator[](size_t index) const { return m_ptr[index]; }
 
     constexpr T* data() const { return m_ptr; }
     constexpr size_t size() const { return m_size; }
@@ -203,25 +211,11 @@
 #ifdef TESTING
         SimpleArrayTesting::reallocCount++;
 #endif
-        if constexpr (!std::is_pod<T>())
-        {
-            // Call destructor for elements when sizing down.
-            for (T *element = this->m_ptr + size, *end = this->m_ptr + this->m_size; element < end;
-                 element++)
-            {
-                element->~T();
-            }
-        }
+        // Call destructor for elements when sizing down.
+        SimpleArrayHelper<T>::DestructArray(this->m_ptr + size, this->m_ptr + this->m_size);
         this->m_ptr = static_cast<T*>(realloc(this->m_ptr, size * sizeof(T)));
-        if constexpr (!std::is_pod<T>())
-        {
-            // Call constructor for elements when sizing up.
-            for (T *element = this->m_ptr + this->m_size, *end = this->m_ptr + size; element < end;
-                 element++)
-            {
-                new (element) T();
-            }
-        }
+        // Call constructor for elements when sizing up.
+        SimpleArrayHelper<T>::DefaultConstructArray(this->m_ptr + this->m_size, this->m_ptr + size);
         this->m_size = size;
     }
 
@@ -229,7 +223,7 @@
 };
 
 template <typename T>
-constexpr SimpleArray<T>::SimpleArray(SimpleArrayBuilder<T>&& other) : m_size(other.size())
+SimpleArray<T>::SimpleArray(SimpleArrayBuilder<T>&& other) : m_size(other.size())
 {
     // Bring the capacity down to the actual size (this should keep the same
     // ptr, but that's not guaranteed, so we copy the ptr after the realloc).
diff --git a/include/rive/span.hpp b/include/rive/span.hpp
index 5dac1b5..3dd8ea1 100644
--- a/include/rive/span.hpp
+++ b/include/rive/span.hpp
@@ -34,11 +34,9 @@
     constexpr Span(const Span<U>& that) : Span(that.data(), that.size())
     {}
     constexpr Span(const Span&) = default;
-    template <typename Container> constexpr Span(Container& c) : Span{std::data(c), std::size(c)} {}
-    constexpr Span(std::initializer_list<T> il) : Span(std::data(il), std::size(il)) {}
-    template <size_t N> constexpr Span(T (&a)[N]) : Span(a, N) {}
+    template <typename Container> constexpr Span(Container& c) : Span(c.data(), c.size()) {}
 
-    constexpr T& operator[](size_t index) const
+    T& operator[](size_t index) const
     {
         assert(index < m_Size);
         return m_Ptr[index];
@@ -57,14 +55,9 @@
     // returns byte-size of the entire span
     constexpr size_t size_bytes() const { return m_Size * sizeof(T); }
 
-    constexpr int count() const
-    {
-        const int n = static_cast<int>(m_Size);
-        assert(n >= 0);
-        return n;
-    }
+    constexpr size_t count() const { return m_Size; }
 
-    constexpr Span<T> subset(size_t offset, size_t size) const
+    Span<T> subset(size_t offset, size_t size) const
     {
         assert(offset <= m_Size);
         assert(size <= m_Size - offset);
@@ -82,6 +75,8 @@
     typedef size_t size_type;
 };
 
+template <typename T> Span<T> make_span(T* ptr, size_t size) { return Span<T>(ptr, size); }
+
 } // namespace rive
 
 #endif
diff --git a/src/animation/animation_state.cpp b/src/animation/animation_state.cpp
index 342ed42..4e4dcf9 100644
--- a/src/animation/animation_state.cpp
+++ b/src/animation/animation_state.cpp
@@ -8,5 +8,5 @@
 
 std::unique_ptr<StateInstance> AnimationState::makeInstance(ArtboardInstance* instance) const
 {
-    return std::make_unique<AnimationStateInstance>(this, instance);
+    return rivestd::make_unique<AnimationStateInstance>(this, instance);
 }
diff --git a/src/animation/blend_state_1d.cpp b/src/animation/blend_state_1d.cpp
index b77422d..ec95728 100644
--- a/src/animation/blend_state_1d.cpp
+++ b/src/animation/blend_state_1d.cpp
@@ -8,7 +8,7 @@
 
 std::unique_ptr<StateInstance> BlendState1D::makeInstance(ArtboardInstance* instance) const
 {
-    return std::make_unique<BlendState1DInstance>(this, instance);
+    return rivestd::make_unique<BlendState1DInstance>(this, instance);
 }
 
 StatusCode BlendState1D::import(ImportStack& importStack)
diff --git a/src/animation/blend_state_direct.cpp b/src/animation/blend_state_direct.cpp
index 92e96d4..c9b4597 100644
--- a/src/animation/blend_state_direct.cpp
+++ b/src/animation/blend_state_direct.cpp
@@ -8,5 +8,5 @@
 
 std::unique_ptr<StateInstance> BlendStateDirect::makeInstance(ArtboardInstance* instance) const
 {
-    return std::make_unique<BlendStateDirectInstance>(this, instance);
+    return rivestd::make_unique<BlendStateDirectInstance>(this, instance);
 }
\ No newline at end of file
diff --git a/src/animation/layer_state.cpp b/src/animation/layer_state.cpp
index ffce4ab..173267a 100644
--- a/src/animation/layer_state.cpp
+++ b/src/animation/layer_state.cpp
@@ -58,5 +58,5 @@
 
 std::unique_ptr<StateInstance> LayerState::makeInstance(ArtboardInstance* instance) const
 {
-    return std::make_unique<SystemStateInstance>(this, instance);
+    return rivestd::make_unique<SystemStateInstance>(this, instance);
 }
\ No newline at end of file
diff --git a/src/animation/linear_animation.cpp b/src/animation/linear_animation.cpp
index f643c7a..9ddfd0a 100644
--- a/src/animation/linear_animation.cpp
+++ b/src/animation/linear_animation.cpp
@@ -92,5 +92,5 @@
             int direction = ((int)(seconds / (endSeconds() - startSeconds()))) % 2;
             return direction == 0 ? localTime + startSeconds() : endSeconds() - localTime;
     }
-    RIVE_UNREACHABLE;
+    RIVE_UNREACHABLE();
 }
\ No newline at end of file
diff --git a/src/animation/nested_linear_animation.cpp b/src/animation/nested_linear_animation.cpp
index 069d276..2d53dee 100644
--- a/src/animation/nested_linear_animation.cpp
+++ b/src/animation/nested_linear_animation.cpp
@@ -9,5 +9,5 @@
 void NestedLinearAnimation::initializeAnimation(ArtboardInstance* artboard)
 {
     m_AnimationInstance =
-        std::make_unique<LinearAnimationInstance>(artboard->animation(animationId()), artboard);
+        rivestd::make_unique<LinearAnimationInstance>(artboard->animation(animationId()), artboard);
 }
\ No newline at end of file
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp
index 6ae9f34..e0a7132 100644
--- a/src/animation/state_machine_instance.cpp
+++ b/src/animation/state_machine_instance.cpp
@@ -434,7 +434,7 @@
                 auto shape = m_ArtboardInstance->resolve(id);
                 if (shape != nullptr && shape->is<Shape>())
                 {
-                    auto hs = std::make_unique<HitShape>(shape->as<Shape>());
+                    auto hs = rivestd::make_unique<HitShape>(shape->as<Shape>());
                     hitShapeLookup[id] = hitShape = hs.get();
                     m_HitShapes.push_back(std::move(hs));
                 }
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 53dbe47..14fd0ea 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -735,25 +735,25 @@
 std::unique_ptr<LinearAnimationInstance> ArtboardInstance::animationAt(size_t index)
 {
     auto la = this->animation(index);
-    return la ? std::make_unique<LinearAnimationInstance>(la, this) : nullptr;
+    return la ? rivestd::make_unique<LinearAnimationInstance>(la, this) : nullptr;
 }
 
 std::unique_ptr<LinearAnimationInstance> ArtboardInstance::animationNamed(const std::string& name)
 {
     auto la = this->animation(name);
-    return la ? std::make_unique<LinearAnimationInstance>(la, this) : nullptr;
+    return la ? rivestd::make_unique<LinearAnimationInstance>(la, this) : nullptr;
 }
 
 std::unique_ptr<StateMachineInstance> ArtboardInstance::stateMachineAt(size_t index)
 {
     auto sm = this->stateMachine(index);
-    return sm ? std::make_unique<StateMachineInstance>(sm, this) : nullptr;
+    return sm ? rivestd::make_unique<StateMachineInstance>(sm, this) : nullptr;
 }
 
 std::unique_ptr<StateMachineInstance> ArtboardInstance::stateMachineNamed(const std::string& name)
 {
     auto sm = this->stateMachine(name);
-    return sm ? std::make_unique<StateMachineInstance>(sm, this) : nullptr;
+    return sm ? rivestd::make_unique<StateMachineInstance>(sm, this) : nullptr;
 }
 
 std::unique_ptr<StateMachineInstance> ArtboardInstance::defaultStateMachine()
diff --git a/src/math/contour_measure.cpp b/src/math/contour_measure.cpp
index 5971a2d..49b5be2 100644
--- a/src/math/contour_measure.cpp
+++ b/src/math/contour_measure.cpp
@@ -385,7 +385,8 @@
 
     for (; m_iter != m_end; ++m_iter)
     {
-        auto [verb, iterPts] = *m_iter;
+        PathVerb verb = std::get<0>(*m_iter);
+        const Vec2D* iterPts = std::get<1>(*m_iter);
         if (verb == PathVerb::move)
         {
             if (!pts.empty())
@@ -439,7 +440,7 @@
             }
             break;
             case PathVerb::move:
-                RIVE_UNREACHABLE; // Handled above.
+                RIVE_UNREACHABLE(); // Handled above.
         }
     }
 
diff --git a/src/math/raw_path.cpp b/src/math/raw_path.cpp
index 0cc01a2..1529900 100644
--- a/src/math/raw_path.cpp
+++ b/src/math/raw_path.cpp
@@ -270,8 +270,10 @@
 
 void RawPath::addTo(CommandPath* result) const
 {
-    for (auto [verb, pts] : *this)
+    for (auto iter : *this)
     {
+        PathVerb verb = std::get<0>(iter);
+        const Vec2D* pts = std::get<1>(iter);
         switch (verb)
         {
             case PathVerb::move:
@@ -287,7 +289,7 @@
                 result->close();
                 break;
             case PathVerb::quad:
-                RIVE_UNREACHABLE;
+                RIVE_UNREACHABLE();
         }
     }
 }
diff --git a/src/shapes/path.cpp b/src/shapes/path.cpp
index bcb4aca..902980a 100644
--- a/src/shapes/path.cpp
+++ b/src/shapes/path.cpp
@@ -351,7 +351,7 @@
                     deletePrevious = true;
                     break;
                 }
-                [[fallthrough]];
+                RIVE_FALLTHROUGH;
             }
             default:
                 if (deletePrevious)
diff --git a/tess/src/sokol/sokol_tess_renderer.cpp b/tess/src/sokol/sokol_tess_renderer.cpp
index c01de67..5078ec1 100644
--- a/tess/src/sokol/sokol_tess_renderer.cpp
+++ b/tess/src/sokol/sokol_tess_renderer.cpp
@@ -141,12 +141,12 @@
 // Returns a full-formed RenderPath -- can be treated as immutable
 std::unique_ptr<RenderPath> SokolFactory::makeRenderPath(RawPath& rawPath, FillRule rule)
 {
-    return std::make_unique<SokolRenderPath>(rawPath, rule);
+    return rivestd::make_unique<SokolRenderPath>(rawPath, rule);
 }
 
 std::unique_ptr<RenderPath> SokolFactory::makeEmptyRenderPath()
 {
-    return std::make_unique<SokolRenderPath>();
+    return rivestd::make_unique<SokolRenderPath>();
 }
 
 class SokolBuffer : public RenderBuffer
@@ -740,7 +740,7 @@
                 m_strokeDirty = false;
                 break;
             case RenderPaintStyle::stroke:
-                m_stroke = std::make_unique<ContourStroke>();
+                m_stroke = rivestd::make_unique<ContourStroke>();
                 m_strokeDirty = true;
                 break;
         }
@@ -897,7 +897,7 @@
 
 std::unique_ptr<RenderPaint> SokolFactory::makeRenderPaint()
 {
-    return std::make_unique<SokolRenderPaint>();
+    return rivestd::make_unique<SokolRenderPaint>();
 }
 
 void SokolTessRenderer::restore()
diff --git a/test/binary_reader_test.cpp b/test/binary_reader_test.cpp
index bb43a84..da924fb 100644
--- a/test/binary_reader_test.cpp
+++ b/test/binary_reader_test.cpp
@@ -40,7 +40,7 @@
     uint8_t* p = storage;
 
     p = packvarint(storage, value);
-    rive::BinaryReader reader(rive::Span(storage, p - storage));
+    rive::BinaryReader reader(rive::make_span(storage, p - storage));
 
     auto newValue = reader.readVarUintAs<T>();
 
diff --git a/test/clip_test.cpp b/test/clip_test.cpp
index e040633..9185294 100644
--- a/test/clip_test.cpp
+++ b/test/clip_test.cpp
@@ -50,7 +50,7 @@
     std::unique_ptr<rive::RenderPath> makeRenderPath(rive::RawPath& rawPath,
                                                      rive::FillRule) override
     {
-        return std::make_unique<ClipTestRenderPath>(rawPath);
+        return rivestd::make_unique<ClipTestRenderPath>(rawPath);
     }
 };
 
diff --git a/test/contour_measure_test.cpp b/test/contour_measure_test.cpp
index aa6c77c..abf0635 100644
--- a/test/contour_measure_test.cpp
+++ b/test/contour_measure_test.cpp
@@ -87,7 +87,7 @@
         {3, 0},
         {3, 4},
     };
-    auto span = Span(pts, sizeof(pts) / sizeof(pts[0]));
+    auto span = make_span(pts, sizeof(pts) / sizeof(pts[0]));
 
     // We expect 3 measurable contours out of this: 7, 16, 7
     // the others should be skipped since they are empty (len == 0)
diff --git a/test/hittest_test.cpp b/test/hittest_test.cpp
index 46e461e..fa87032 100644
--- a/test/hittest_test.cpp
+++ b/test/hittest_test.cpp
@@ -55,5 +55,5 @@
         1,
         2,
     };
-    REQUIRE(HitTester::testMesh(area, Span(verts, 3), Span(indices, 3)));
+    REQUIRE(HitTester::testMesh(area, make_span(verts, 3), make_span(indices, 3)));
 }
diff --git a/test/mat2d_test.cpp b/test/mat2d_test.cpp
index 1d68cd8..c21df78 100644
--- a/test/mat2d_test.cpp
+++ b/test/mat2d_test.cpp
@@ -107,12 +107,14 @@
     // success = givingNegativeNearlyZeros.getMinMaxScales(scales);
     // CHECK(success && 0 == scales[0]);
 
-    Mat2D baseMats[] = {scale, rot90Scale, rotate, translate};
-    Mat2D mats[2 * std::size(baseMats)];
-    for (size_t i = 0; i < std::size(baseMats); ++i)
+    constexpr int kNumBaseMats = 4;
+    Mat2D baseMats[kNumBaseMats] = {scale, rot90Scale, rotate, translate};
+    constexpr int kNumMats = 2 * kNumBaseMats;
+    Mat2D mats[kNumMats];
+    for (size_t i = 0; i < kNumBaseMats; ++i)
     {
         mats[i] = baseMats[i];
-        bool invertible = mats[i].invert(&mats[i + std::size(baseMats)]);
+        bool invertible = mats[i].invert(&mats[i + kNumBaseMats]);
         REQUIRE(invertible);
     }
     srand(0);
@@ -121,7 +123,7 @@
         Mat2D mat;
         for (int i = 0; i < 4; ++i)
         {
-            int x = rand() % std::size(mats);
+            int x = rand() % kNumMats;
             mat = mats[x] * mat;
         }
 
@@ -135,8 +137,9 @@
         static const float gVectorScaleTol = (105 * 1.f) / 100;
         static const float gCloseScaleTol = (97 * 1.f) / 100;
         float max = 0, min = std::numeric_limits<float>::max();
-        Vec2D vectors[1000];
-        for (size_t i = 0; i < std::size(vectors); ++i)
+        constexpr int kNumVectors = 1000;
+        Vec2D vectors[kNumVectors];
+        for (size_t i = 0; i < kNumVectors; ++i)
         {
             vectors[i].x = rand() * 2.f / static_cast<float>(RAND_MAX) - 1;
             vectors[i].y = rand() * 2.f / static_cast<float>(RAND_MAX) - 1;
@@ -144,7 +147,7 @@
             vectors[i] = {mat[0] * vectors[i].x + mat[2] * vectors[i].y,
                           mat[1] * vectors[i].x + mat[3] * vectors[i].y};
         }
-        for (size_t i = 0; i < std::size(vectors); ++i)
+        for (size_t i = 0; i < kNumVectors; ++i)
         {
             float d = vectors[i].length();
             REQUIRE(d / maxScale < gVectorScaleTol);
diff --git a/test/path_test.cpp b/test/path_test.cpp
index 673c54d..bfc6a33 100644
--- a/test/path_test.cpp
+++ b/test/path_test.cpp
@@ -75,7 +75,7 @@
 public:
     std::unique_ptr<rive::RenderPath> makeEmptyRenderPath() override
     {
-        return std::make_unique<TestRenderPath>();
+        return rivestd::make_unique<TestRenderPath>();
     }
 };
 } // namespace
diff --git a/test/raw_path_test.cpp b/test/raw_path_test.cpp
index 0eca6c3..815ba2e 100644
--- a/test/raw_path_test.cpp
+++ b/test/raw_path_test.cpp
@@ -91,7 +91,8 @@
                        std::vector<Vec2D> expectedPts)
 {
     REQUIRE(iter != end);
-    auto [verb, pts] = *iter;
+    PathVerb verb = std::get<0>(*iter);
+    const Vec2D* pts = std::get<1>(*iter);
     REQUIRE(verb == expectedVerb);
     for (size_t i = 0; i < expectedPts.size(); ++i)
     {
@@ -113,7 +114,8 @@
         rp.quadTo(5, 6, 7, 8);
         rp.cubicTo(9, 10, 11, 12, 13, 14);
         rp.close();
-        auto [iter, end] = std::make_tuple(rp.begin(), rp.end());
+        auto iter = rp.begin();
+        auto end = rp.end();
         check_iter(iter, end, PathVerb::move, {{1, 2}});
         check_iter(iter, end, PathVerb::line, {{1, 2}, {3, 4}});
         check_iter(iter, end, PathVerb::quad, {{3, 4}, {5, 6}, {7, 8}});
diff --git a/test/simd_test.cpp b/test/simd_test.cpp
index 0ac8a64..2fff755 100644
--- a/test/simd_test.cpp
+++ b/test/simd_test.cpp
@@ -289,8 +289,6 @@
 
     // Returns lo if x == NaN, but std::clamp() returns NaN.
     CHECK(simd::clamp<float, 1>(kNaN, 1, 2).x == 1);
-    CHECK(std::clamp<float>(kNaN, 1, 2) != 1);
-    CHECK(std::isnan(std::clamp<float>(kNaN, 1, 2)));
 
     // Returns hi if hi <= lo.
     CHECK(simd::clamp<float, 1>(3, 2, 1).x == 1);
diff --git a/test/simple_array_test.cpp b/test/simple_array_test.cpp
index 9b4e206..82b5fc2 100644
--- a/test/simple_array_test.cpp
+++ b/test/simple_array_test.cpp
@@ -19,7 +19,8 @@
 
 TEST_CASE("simple array can be created", "[simple array]")
 {
-    SimpleArray<int> array({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
+    std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+    SimpleArray<int> array(v);
 
     REQUIRE(!array.empty());
     REQUIRE(array.size() == 10);
@@ -40,7 +41,7 @@
 TEST_CASE("can iterate simple array", "[simple array]")
 {
     const int carray[] = {2, 4, 8, 16};
-    SimpleArray<int> array(carray);
+    SimpleArray<int> array(carray, 4);
     int expect = 2;
     for (auto value : array)
     {
@@ -89,7 +90,8 @@
 {
     SimpleArrayTesting::resetCounters();
 
-    SimpleArray<uint32_t> numbersA({33, 22, 44, 66});
+    std::vector<uint32_t> vA{33, 22, 44, 66};
+    SimpleArray<uint32_t> numbersA(vA);
 
     StructA dataA = {std::move(numbersA)};
     // We moved the data so expect only one alloc and 0 reallocs.
@@ -98,7 +100,8 @@
     REQUIRE(dataA.numbers.size() == 4);
     REQUIRE(numbersA.size() == 0);
 
-    SimpleArray<uint32_t> numbersB({1, 2, 3});
+    std::vector<uint32_t> vB{1, 2, 3};
+    SimpleArray<uint32_t> numbersB(vB);
 
     StructA dataB = {std::move(numbersB)};
     REQUIRE(SimpleArrayTesting::mallocCount == 2);
diff --git a/test/span_test.cpp b/test/span_test.cpp
index a2ddfb6..874b766 100644
--- a/test/span_test.cpp
+++ b/test/span_test.cpp
@@ -55,9 +55,7 @@
     funcb({carray, 4});
 
     int array[] = {1, 2, 3, 4};
-    funca(array);
     funca({array, 4});
-    funcb(array);
     funcb({array, 4});
 
     std::vector<int> v;
@@ -69,7 +67,7 @@
 {
     const int carray[] = {2, 4, 8, 16};
 
-    auto span = Span(carray);
+    auto span = make_span(carray, 4);
     int expect = 2;
     for (auto value : span)
     {
diff --git a/utils/no_op_factory.cpp b/utils/no_op_factory.cpp
index 9058301..2178aba 100644
--- a/utils/no_op_factory.cpp
+++ b/utils/no_op_factory.cpp
@@ -66,17 +66,17 @@
 
 std::unique_ptr<RenderPath> NoOpFactory::makeRenderPath(RawPath&, FillRule)
 {
-    return std::make_unique<NoOpRenderPath>();
+    return rivestd::make_unique<NoOpRenderPath>();
 }
 
 std::unique_ptr<RenderPath> NoOpFactory::makeEmptyRenderPath()
 {
-    return std::make_unique<NoOpRenderPath>();
+    return rivestd::make_unique<NoOpRenderPath>();
 }
 
 std::unique_ptr<RenderPaint> NoOpFactory::makeRenderPaint()
 {
-    return std::make_unique<NoOpRenderPaint>();
+    return rivestd::make_unique<NoOpRenderPaint>();
 }
 
 std::unique_ptr<RenderImage> NoOpFactory::decodeImage(Span<const uint8_t>)
diff --git a/viewer/src/sample_tools/sample_atlas_packer.cpp b/viewer/src/sample_tools/sample_atlas_packer.cpp
index b9a6f88..5848ef7 100644
--- a/viewer/src/sample_tools/sample_atlas_packer.cpp
+++ b/viewer/src/sample_tools/sample_atlas_packer.cpp
@@ -40,9 +40,9 @@
                 bitmap->pixelFormat(Bitmap::PixelFormat::RGBA);
             }
 
-            return std::make_unique<AtlasRenderImage>(bitmap->bytes(),
-                                                      bitmap->width(),
-                                                      bitmap->height());
+            return rivestd::make_unique<AtlasRenderImage>(bitmap->bytes(),
+                                                          bitmap->width(),
+                                                          bitmap->height());
         }
         return nullptr;
     }
@@ -236,10 +236,10 @@
             // renderer (and hence will know which RenderImage they need to
             // make).
 
-            imageAsset->renderImage(std::make_unique<SokolRenderImage>(imageResource,
-                                                                       location.width,
-                                                                       location.height,
-                                                                       location.transform));
+            imageAsset->renderImage(rivestd::make_unique<SokolRenderImage>(imageResource,
+                                                                           location.width,
+                                                                           location.height,
+                                                                           location.transform));
         }
     }
 }
diff --git a/viewer/src/skia/skia_host.cpp b/viewer/src/skia/skia_host.cpp
index 2c44b6b..a24e4d8 100644
--- a/viewer/src/skia/skia_host.cpp
+++ b/viewer/src/skia/skia_host.cpp
@@ -121,7 +121,7 @@
     }
 };
 
-std::unique_ptr<ViewerHost> ViewerHost::Make() { return std::make_unique<SkiaViewerHost>(); }
+std::unique_ptr<ViewerHost> ViewerHost::Make() { return rivestd::make_unique<SkiaViewerHost>(); }
 
 rive::Factory* ViewerHost::Factory()
 {
diff --git a/viewer/src/tess/bitmap_decoder.cpp b/viewer/src/tess/bitmap_decoder.cpp
index 112f9d1..cd4a286 100644
--- a/viewer/src/tess/bitmap_decoder.cpp
+++ b/viewer/src/tess/bitmap_decoder.cpp
@@ -100,7 +100,7 @@
         return;
     }
     auto nextByteSize = byteSize(format);
-    auto nextBytes = std::make_unique<uint8_t[]>(nextByteSize);
+    auto nextBytes = rivestd::make_unique<uint8_t[]>(nextByteSize);
 
     auto fromBytesPerPixel = bytesPerPixel(m_PixelFormat);
     auto toBytesPerPixel = bytesPerPixel(format);
diff --git a/viewer/src/tess/decode_png.cpp b/viewer/src/tess/decode_png.cpp
index 1a7a6b0..63998f4 100644
--- a/viewer/src/tess/decode_png.cpp
+++ b/viewer/src/tess/decode_png.cpp
@@ -135,6 +135,6 @@
             pixelFormat = Bitmap::PixelFormat::R;
             break;
     }
-    return std::make_unique<Bitmap>(width, height, pixelFormat, pixelBuffer);
+    return rivestd::make_unique<Bitmap>(width, height, pixelFormat, pixelBuffer);
 }
 #endif
\ No newline at end of file
diff --git a/viewer/src/tess/tess_host.cpp b/viewer/src/tess/tess_host.cpp
index b25a345..416f910 100644
--- a/viewer/src/tess/tess_host.cpp
+++ b/viewer/src/tess/tess_host.cpp
@@ -17,7 +17,7 @@
 
     bool init(sg_pass_action*, int width, int height) override
     {
-        m_renderer = std::make_unique<rive::SokolTessRenderer>();
+        m_renderer = rivestd::make_unique<rive::SokolTessRenderer>();
         m_renderer->orthographicProjection(0.0f, width, height, 0.0f, 0.0f, 1.0f);
         return true;
     }
@@ -37,7 +37,7 @@
     }
 };
 
-std::unique_ptr<ViewerHost> ViewerHost::Make() { return std::make_unique<TessViewerHost>(); }
+std::unique_ptr<ViewerHost> ViewerHost::Make() { return rivestd::make_unique<TessViewerHost>(); }
 
 rive::Factory* ViewerHost::Factory()
 {
diff --git a/viewer/src/tess/viewer_sokol_factory.cpp b/viewer/src/tess/viewer_sokol_factory.cpp
index bc0b6fb..a403693 100644
--- a/viewer/src/tess/viewer_sokol_factory.cpp
+++ b/viewer/src/tess/viewer_sokol_factory.cpp
@@ -26,10 +26,10 @@
             new rive::SokolRenderImageResource(bitmap->bytes(), bitmap->width(), bitmap->height()));
 
         static rive::Mat2D identity;
-        return std::make_unique<rive::SokolRenderImage>(imageGpuResource,
-                                                        bitmap->width(),
-                                                        bitmap->height(),
-                                                        identity);
+        return rivestd::make_unique<rive::SokolRenderImage>(imageGpuResource,
+                                                            bitmap->width(),
+                                                            bitmap->height(),
+                                                            identity);
     }
     return nullptr;
 }
diff --git a/viewer/src/viewer_content/image_content.cpp b/viewer/src/viewer_content/image_content.cpp
index 055c038..ab24415 100644
--- a/viewer/src/viewer_content/image_content.cpp
+++ b/viewer/src/viewer_content/image_content.cpp
@@ -28,7 +28,7 @@
     auto image = RiveFactory()->decodeImage(bytes);
     if (image)
     {
-        return std::make_unique<ImageContent>(std::move(image));
+        return rivestd::make_unique<ImageContent>(std::move(image));
     }
     return nullptr;
 }
diff --git a/viewer/src/viewer_content/scene_content.cpp b/viewer/src/viewer_content/scene_content.cpp
index 6bd1129..196000f 100644
--- a/viewer/src/viewer_content/scene_content.cpp
+++ b/viewer/src/viewer_content/scene_content.cpp
@@ -386,7 +386,7 @@
     auto bytes = LoadFile(filename);
     if (auto file = rive::File::import(bytes, RiveFactory()))
     {
-        return std::make_unique<SceneContent>(filename, std::move(file));
+        return rivestd::make_unique<SceneContent>(filename, std::move(file));
     }
     return nullptr;
 }
\ No newline at end of file
diff --git a/viewer/src/viewer_content/text_content.cpp b/viewer/src/viewer_content/text_content.cpp
index fecc4b1..a84612f 100644
--- a/viewer/src/viewer_content/text_content.cpp
+++ b/viewer/src/viewer_content/text_content.cpp
@@ -392,7 +392,7 @@
 {
     if (ends_width(filename, ".svg"))
     {
-        return std::make_unique<TextContent>();
+        return rivestd::make_unique<TextContent>();
     }
     return nullptr;
 }
diff --git a/viewer/src/viewer_content/textpath_content.cpp b/viewer/src/viewer_content/textpath_content.cpp
index 0b78ae6..4f90389 100644
--- a/viewer/src/viewer_content/textpath_content.cpp
+++ b/viewer/src/viewer_content/textpath_content.cpp
@@ -402,7 +402,7 @@
 
 std::unique_ptr<ViewerContent> ViewerContent::TextPath(const char filename[])
 {
-    return std::make_unique<TextPathContent>();
+    return rivestd::make_unique<TextPathContent>();
 }
 #else
 std::unique_ptr<ViewerContent> ViewerContent::TextPath(const char filename[]) { return nullptr; }
diff --git a/viewer/src/viewer_content/trimpath_content.cpp b/viewer/src/viewer_content/trimpath_content.cpp
index f72bd4a..2f4dac2 100644
--- a/viewer/src/viewer_content/trimpath_content.cpp
+++ b/viewer/src/viewer_content/trimpath_content.cpp
@@ -170,5 +170,5 @@
 
 std::unique_ptr<ViewerContent> ViewerContent::TrimPath(const char[])
 {
-    return std::make_unique<TrimPathContent>();
+    return rivestd::make_unique<TrimPathContent>();
 }