diff --git a/src/util/any.hpp b/src/util/any.hpp
index 50b29ec78eda6afbd853cf356d4a235b3ae0ffd9..83bf63a35a92bf79b18bc3490123d416c9e0e563 100644
--- a/src/util/any.hpp
+++ b/src/util/any.hpp
@@ -103,26 +103,13 @@ private:
     template <typename T>
     struct model: public interface {
         ~model() = default;
-
         model(const T& other): value(other) {}
-
         model(T&& other): value(std::move(other)) {}
 
-        interface* copy() override {
-            return new model<T>(*this);
-        }
-
-        const std::type_info& type() override {
-            return typeid(T);
-        }
-
-        void* pointer() override {
-            return &value;
-        }
-
-        const void* pointer() const override {
-            return &value;
-        }
+        interface* copy() override { return new model<T>(*this); }
+        const std::type_info& type() override { return typeid(T); }
+        void* pointer() override { return &value; }
+        const void* pointer() const override { return &value; }
 
         T value;
     };
@@ -130,7 +117,6 @@ private:
     std::unique_ptr<interface> state_;
 
 protected:
-
     template <typename T>
     friend const T* any_cast(const any* operand);
 
diff --git a/src/util/unique_any.hpp b/src/util/unique_any.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..758356db528e949a4e9a99d2d3199cf867d38b4c
--- /dev/null
+++ b/src/util/unique_any.hpp
@@ -0,0 +1,211 @@
+#pragma once
+
+#include <memory>
+#include <typeinfo>
+#include <type_traits>
+
+#include <util/any.hpp>
+#include <util/meta.hpp>
+
+// A non copyable variant of util::any.
+// The two main use cases for such a container are
+//  1. storing types that are not copyable.
+//  2. ensuring that no copies are made of copyable types that have to be stored
+//     in a type-erased container.
+//
+// unique_any has the same semantics as any with the execption of copy and copy
+// assignment, which are explicitly forbidden for all contained types.
+// The requirement that the contained type be copy constructable has also been
+// relaxed.
+//
+// The any_cast non-member functions have been overridden for unique_any, with
+// the same semantics as for any.
+// This makes it possible to copy the underlying stored type if the type is
+// copyable. For example, the following code will compile and execute as
+// expected.
+//
+//  unique_any<int> a(3);
+//  int& ref = any_cast<int&>(a); // take a reference
+//  ref = 42;                     // update contained value via reference
+//  int  val = any_cast<int>(a);  // take a copy
+//  assert(val==42);
+//
+// If the underlying type is not copyable, only references may be taken
+//
+//  unique_any<nocopy_t> a();
+//  nocopy_t& ref        = any_cast<nocopy_t&>(a);       // ok
+//  const nocopy_t& cref = any_cast<const nocopy_t&>(a); // ok
+//  nocopy_t v           = any_cast<nocopy_t>(a);        // compile time error
+//
+// An lvalue can be created by moving from the contained object:
+//
+//  nocopy_t v = any_cast<nocopy_t&&>(std::move(a)); // ok
+//
+// After which a is in moved from state.
+
+
+namespace nest {
+namespace mc {
+namespace util {
+
+class unique_any {
+public:
+    constexpr unique_any() = default;
+
+    unique_any(unique_any&& other) noexcept {
+        std::swap(other.state_, state_);
+    }
+
+    template <
+        typename T,
+        typename = typename util::enable_if_t<!std::is_same<util::decay_t<T>, unique_any>::value>
+    >
+    unique_any(T&& other) {
+        using contained_type = util::decay_t<T>;
+        state_.reset(new model<contained_type>(std::forward<T>(other)));
+    }
+
+    unique_any& operator=(unique_any&& other) noexcept {
+        swap(other);
+        return *this;
+    }
+
+    template <
+        typename T,
+        typename = typename util::enable_if_t<!std::is_same<util::decay_t<T>, unique_any>::value>
+    >
+    unique_any& operator=(T&& other) {
+        using contained_type = util::decay_t<T>;
+        state_.reset(new model<contained_type>(std::forward<T>(other)));
+        return *this;
+    }
+
+    void reset() noexcept {
+        state_.reset(nullptr);
+    }
+
+    void swap(unique_any& other) noexcept {
+        std::swap(other.state_, state_);
+    }
+
+    bool has_value() const noexcept {
+        return (bool)state_;
+    }
+
+    const std::type_info& type() const noexcept {
+        return has_value()? state_->type(): typeid(void);
+    }
+
+private:
+    struct interface {
+        virtual ~interface() = default;
+        virtual const std::type_info& type() = 0;
+        virtual void* pointer() = 0;
+        virtual const void* pointer() const = 0;
+    };
+
+    template <typename T>
+    struct model: public interface {
+        ~model() = default;
+        model(const T& other): value(other) {}
+        model(T&& other): value(std::move(other)) {}
+
+        const std::type_info& type() override { return typeid(T); }
+        void* pointer() override { return &value; }
+        const void* pointer() const override { return &value; }
+
+        T value;
+    };
+
+    std::unique_ptr<interface> state_;
+
+protected:
+    template <typename T>
+    friend const T* any_cast(const unique_any* operand);
+
+    template <typename T>
+    friend T* any_cast(unique_any* operand);
+
+    template <typename T>
+    T* unsafe_cast() {
+        return static_cast<T*>(state_->pointer());
+    }
+
+    template <typename T>
+    const T* unsafe_cast() const {
+        return static_cast<const T*>(state_->pointer());
+    }
+};
+
+// If operand is not a null pointer, and the typeid of the requested T matches
+// that of the contents of operand, a pointer to the value contained by operand,
+// otherwise a null pointer.
+template<class T>
+const T* any_cast(const unique_any* operand) {
+    if (operand && operand->type()==typeid(T)) {
+        return operand->unsafe_cast<T>();
+    }
+    return nullptr;
+}
+
+// If operand is not a null pointer, and the typeid of the requested T matches
+// that of the contents of operand, a pointer to the value contained by operand,
+// otherwise a null pointer.
+template<class T>
+T* any_cast(unique_any* operand) {
+    if (operand && operand->type()==typeid(T)) {
+        return operand->unsafe_cast<T>();
+    }
+    return nullptr;
+}
+
+template<class T>
+T any_cast(const unique_any& operand) {
+    using U = impl::any_cast_remove_qual<T>;
+    static_assert(std::is_constructible<T, const U&>::value,
+        "any_cast type can't construct copy of contained object");
+
+    auto ptr = any_cast<U>(&operand);
+    if (ptr==nullptr) {
+        throw bad_any_cast();
+    }
+    return static_cast<T>(*ptr);
+}
+
+template<class T>
+T any_cast(unique_any& operand) {
+    using U = impl::any_cast_remove_qual<T>;
+    static_assert(std::is_constructible<T, U&>::value,
+        "any_cast type can't construct copy of contained object");
+
+    auto ptr = any_cast<U>(&operand);
+    if (ptr==nullptr) {
+        throw bad_any_cast();
+    }
+    return static_cast<T>(*ptr);
+}
+
+template<class T>
+T any_cast(unique_any&& operand) {
+    using U = impl::any_cast_remove_qual<T>;
+
+    static_assert(std::is_constructible<T, U&&>::value,
+        "any_cast type can't construct copy of contained object");
+
+    auto ptr = any_cast<U>(&operand);
+    if (ptr==nullptr) {
+        throw bad_any_cast();
+    }
+    return static_cast<T>(std::move(*ptr));
+}
+
+// Constructs an any object containing an object of type T, passing the
+// provided arguments to T's constructor.
+template <class T, class... Args>
+unique_any make_unique_any(Args&&... args) {
+    return unique_any(T(std::forward<Args>(args) ...));
+}
+
+} // namespace util
+} // namespace mc
+} // namespace nest
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 6a5c151ad2d06519192d637d379e2c1f009bd78f..09935997242f8b1b6215a9f91bf4a7d00fd1c848 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -69,6 +69,7 @@ set(TEST_SOURCES
     test_tree.cpp
     test_transform.cpp
     test_uninitialized.cpp
+    test_unique_any.cpp
     test_vector.cpp
 
     # unit test driver
diff --git a/tests/unit/test_any.cpp b/tests/unit/test_any.cpp
index 2564bf4ce4f892a71dae1146f29ba85b67fb71c8..1bfb0627d0e8ce50696c812fb9cb5b63554cc6f6 100644
--- a/tests/unit/test_any.cpp
+++ b/tests/unit/test_any.cpp
@@ -45,13 +45,12 @@ TEST(any, move_construction) {
     util::any moved(std::move(m));
 
     // Check that the expected number of copies and moves were performed.
-    // Note that any_cast(any*) is used instead of any_cast(const any&) because
-    // any_cast(const any&) returns a copy.
-    const auto& cref = *util::any_cast<moveable>(&copied);
+    // Use cast to reference to avoid copy or move of contained object.
+    const auto& cref = util::any_cast<moveable&>(copied);
     EXPECT_EQ(cref.moves, 0);
     EXPECT_EQ(cref.copies, 1);
 
-    const auto& mref = *util::any_cast<moveable>(&moved);
+    const auto& mref = util::any_cast<moveable&>(moved);
     EXPECT_EQ(mref.moves, 1);
     EXPECT_EQ(mref.copies, 0);
 
@@ -59,7 +58,7 @@ TEST(any, move_construction) {
     // constructed value
     util::any fin(std::move(moved));
     EXPECT_FALSE(moved.has_value()); // moved has been moved from and should be empty
-    const auto& fref = *util::any_cast<moveable>(&fin);
+    const auto& fref = util::any_cast<moveable&>(fin);
     EXPECT_EQ(fref.moves, 1);
     EXPECT_EQ(fref.copies, 0);
 
@@ -115,9 +114,6 @@ TEST(any, swap) {
     EXPECT_EQ(typeid(double), any2.type());
 }
 
-TEST(any, constness) {
-}
-
 // These should fail at compile time if the constraint that the contents of any
 // satisfy CopyConstructable. This implementation is rock solid, so they have
 // to be commented out.
@@ -132,7 +128,7 @@ TEST(any, not_copy_constructable) {
 
 // test any_cast(any*)
 //   - these have different behavior to any_cast on reference types
-//   - are used by the any_cast on refernce types
+//   - are used by the any_cast on reference types
 TEST(any, any_cast_ptr) {
 
     // test that valid pointers are returned for int and std::string types
@@ -156,22 +152,43 @@ TEST(any, any_cast_ptr) {
     {
         util::any a(42);
         auto p = util::any_cast<int>(&a);
-        static_assert(std::is_same<int*, decltype(p)>::value,
-                "any_cast(any*) should not return const*");
+
+        // any_cast(any*) should not return const*
+        EXPECT_TRUE((std::is_same<int*, decltype(p)>::value));
     }
     {
         const util::any a(42);
         auto p = util::any_cast<int>(&a);
-        static_assert(std::is_same<const int*, decltype(p)>::value,
-                "any_cast(const any*) should return const*");
+
+        // any_cast(const any*) should return const*
+        EXPECT_TRUE((std::is_same<const int*, decltype(p)>::value));
     }
 }
 
 TEST(any, any_cast_ref) {
-    util::any ai(42);
-    auto i = util::any_cast<int>(ai);
-    EXPECT_EQ(typeid(i), typeid(int));
-    EXPECT_EQ(i, 42);
+    {
+        util::any a(42);
+        auto i = util::any_cast<int>(a);
+        EXPECT_EQ(typeid(i), typeid(int));
+        EXPECT_EQ(i, 42);
+
+        // check that we can take a reference to the
+        // underlying storage
+        auto& r = util::any_cast<int&>(a);
+        r = 3;
+        EXPECT_EQ(3, util::any_cast<int>(a));
+        EXPECT_TRUE((std::is_same<int&, decltype(r)>::value));
+    }
+
+    {   // check that const references can be returned to const objects
+        const util::any a(42);
+        auto& r = util::any_cast<const int&>(a);
+        EXPECT_TRUE((std::is_same<const int&, decltype(r)>::value));
+
+        // The following should fail with a static assertion if uncommented, because
+        // it requests a non-const reference to a const object.
+        //auto& instafail = util::any_cast<int&>(a);
+    }
 }
 
 // test any_cast(any&&)
diff --git a/tests/unit/test_unique_any.cpp b/tests/unit/test_unique_any.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f2b9b4492720544cac93e10408d2986d84d1f77b
--- /dev/null
+++ b/tests/unit/test_unique_any.cpp
@@ -0,0 +1,433 @@
+#include "../gtest.h"
+#include "common.hpp"
+
+#include <iostream>
+
+#include <util/rangeutil.hpp>
+#include <util/span.hpp>
+#include <util/unique_any.hpp>
+
+using namespace nest::mc;
+
+TEST(unique_any, copy_construction) {
+    using util::unique_any;
+
+    unique_any any_int(2);
+    EXPECT_EQ(any_int.type(), typeid(int));
+
+    unique_any any_float(2.0f);
+    EXPECT_EQ(any_float.type(), typeid(float));
+
+    std::string str = "hello";
+    unique_any any_string(str);
+    EXPECT_EQ(any_string.type(), typeid(std::string));
+}
+
+namespace {
+
+    struct moveable {
+        moveable() = default;
+
+        moveable(moveable&& other):
+            moves(other.moves+1), copies(other.copies)
+        {}
+
+        moveable(const moveable& other):
+            moves(other.moves), copies(other.copies+1)
+        {}
+
+        int moves=0;
+        int copies=0;
+    };
+
+}
+
+TEST(unique_any, move_construction) {
+    moveable m;
+
+    util::unique_any copied(m);
+    util::unique_any moved(std::move(m));
+
+    // Check that the expected number of copies and moves were performed.
+    const auto& cref = util::any_cast<const moveable&>(copied);
+    EXPECT_EQ(cref.moves, 0);
+    EXPECT_EQ(cref.copies, 1);
+
+    const auto& mref = util::any_cast<const moveable&>(moved);
+    EXPECT_EQ(mref.moves, 1);
+    EXPECT_EQ(mref.copies, 0);
+
+    // construction by any&& should not make any copies or moves of the
+    // constructed value
+    util::unique_any fin(std::move(moved));
+    EXPECT_FALSE(moved.has_value()); // moved has been moved from and should be empty
+    const auto& fref = util::any_cast<const moveable&>(fin);
+    EXPECT_EQ(fref.moves, 1);
+    EXPECT_EQ(fref.copies, 0);
+
+    const auto value = util::any_cast<moveable>(fin);
+    EXPECT_EQ(value.moves, 1);
+    EXPECT_EQ(value.copies, 1);
+}
+
+TEST(unique_any, type) {
+    using util::unique_any;
+
+    unique_any anyi(42);
+    unique_any anys(std::string("hello"));
+    unique_any anyv(std::vector<int>{1, 2, 3});
+    unique_any any0;
+
+    EXPECT_EQ(typeid(int), anyi.type());
+    EXPECT_EQ(typeid(std::string), anys.type());
+    EXPECT_EQ(typeid(std::vector<int>), anyv.type());
+    EXPECT_EQ(typeid(void), any0.type());
+
+    anyi.reset();
+    EXPECT_EQ(typeid(void), anyi.type());
+
+    anyi = std::true_type();
+    EXPECT_EQ(typeid(std::true_type), anyi.type());
+}
+
+TEST(unique_any, swap) {
+    using util::unique_any;
+    using util::any_cast;
+
+    unique_any any1(42);     // integer
+    unique_any any2(3.14);   // double
+
+    EXPECT_EQ(typeid(int),    any1.type());
+    EXPECT_EQ(typeid(double), any2.type());
+
+    any1.swap(any2);
+
+    EXPECT_EQ(any_cast<int>(any2), 42);
+    EXPECT_EQ(any_cast<double>(any1), 3.14);
+
+    EXPECT_EQ(typeid(double), any1.type());
+    EXPECT_EQ(typeid(int),    any2.type());
+
+    any1.swap(any2);
+
+    EXPECT_EQ(any_cast<double>(any2), 3.14);
+    EXPECT_EQ(any_cast<int>(any1), 42);
+
+    EXPECT_EQ(typeid(int),    any1.type());
+    EXPECT_EQ(typeid(double), any2.type());
+}
+
+TEST(unique_any, not_copy_constructable) {
+    using T = testing::nocopy<int>;
+
+    util::unique_any a(T(42));
+
+    auto& ref = util::any_cast<T&>(a);
+    EXPECT_EQ(ref, 42);
+    ref.value = 100;
+
+    EXPECT_EQ(util::any_cast<T&>(a).value, 100);
+
+    // the following will fail if uncommented, because we are requesting
+    // a copy of an non-copyable type.
+
+    //auto value = util::any_cast<T>(a);
+
+    // Test that we can move the contents of the unique_any.
+    // NOTE: it makes sense to sink a with std::move(a) instead
+    // of the following, because after such an assignment a will
+    // be in a moved from state, and enforcing std::move(a) it
+    // is made clearer in the calling code that a has been invalidated.
+    //     util::any_cast<T&&>(a)
+    T val(util::any_cast<T&&>(std::move(a)));
+    EXPECT_EQ(val.value, 100);
+}
+
+// test any_cast(unique_any*) and any_cast(const unique_any*)
+//   - these have different behavior to any_cast on reference types
+//   - are used by the any_cast on reference types
+TEST(unique_any, any_cast_ptr) {
+    using util::unique_any;
+
+    // test that valid pointers are returned for int and std::string types
+
+    unique_any ai(42);
+    auto ptr_i = util::any_cast<int>(&ai);
+    EXPECT_EQ(*ptr_i, 42);
+
+    unique_any as(std::string("hello"));
+    auto ptr_s = util::any_cast<std::string>(&as);
+    EXPECT_EQ(*ptr_s, "hello");
+
+    // test that exceptions are thrown for invalid casts
+    EXPECT_EQ(util::any_cast<int>(&as), nullptr);
+    EXPECT_EQ(util::any_cast<std::string>(&ai), nullptr);
+    unique_any empty;
+    EXPECT_EQ(util::any_cast<int>(&empty), nullptr);
+    EXPECT_EQ(util::any_cast<int>((util::unique_any*)nullptr), nullptr);
+
+    // Check that constness of the returned pointer matches that the input.
+    // Check that constness of the returned pointer matches that the input.
+    {
+        unique_any a(42);
+        auto p = util::any_cast<int>(&a);
+
+        // any_cast(any*) should not return const*
+        EXPECT_TRUE((std::is_same<int*, decltype(p)>::value));
+    }
+    {
+        const unique_any a(42);
+        auto p = util::any_cast<int>(&a);
+
+        // any_cast(const any*) should return const*
+        EXPECT_TRUE((std::is_same<const int*, decltype(p)>::value));
+    }
+}
+
+// test any_cast(unique_any&)
+TEST(unique_any, any_cast_ref) {
+    util::unique_any ai(42);
+    auto& i = util::any_cast<int&>(ai);
+
+    EXPECT_EQ(typeid(i), typeid(int));
+    EXPECT_EQ(i, 42);
+
+    // any_cast<T>(unique_any&) returns a 
+    i = 100;
+    EXPECT_EQ(util::any_cast<int>(ai), 100);
+}
+
+// test any_cast(const unique_any&)
+TEST(unique_any, any_cast_const_ref) {
+    const util::unique_any ai(42);
+    auto& i = util::any_cast<const int&>(ai);
+
+    EXPECT_EQ(typeid(i), typeid(int));
+    EXPECT_EQ(i, 42);
+
+    EXPECT_TRUE((std::is_same<const int&, decltype(i)>::value));
+}
+
+// test any_cast(unique_any&&)
+TEST(unique_any, any_cast_rvalue) {
+    auto moved = util::any_cast<moveable>(util::unique_any(moveable()));
+    EXPECT_EQ(moved.moves, 2);
+    EXPECT_EQ(moved.copies, 0);
+}
+
+TEST(unique_any, std_swap) {
+    util::unique_any a1(42);
+    util::unique_any a2(3.14);
+
+    auto pi = util::any_cast<int>(&a1);
+    auto pd = util::any_cast<double>(&a2);
+
+    std::swap(a1, a2);
+
+    // test that values were swapped
+    EXPECT_EQ(util::any_cast<int>(a2), 42);
+    EXPECT_EQ(util::any_cast<double>(a1), 3.14);
+
+    // test that underlying pointers did not change
+    EXPECT_EQ(pi, util::any_cast<int>(&a2));
+    EXPECT_EQ(pd, util::any_cast<double>(&a1));
+}
+
+// test operator=(unique_any&&)
+TEST(unique_any, assignment_from_rvalue) {
+    using util::unique_any;
+    using std::string;
+
+    auto str1 = string("one");
+    auto str2 = string("two");
+    unique_any a(str1);
+
+    unique_any b;
+    b = std::move(a); // move assignment
+
+    EXPECT_EQ(str1, util::any_cast<string>(b));
+
+    EXPECT_EQ(nullptr, util::any_cast<string>(&a));
+}
+
+// test template<typename T> operator=(T&&)
+TEST(unique_any, assignment_from_value) {
+    using util::unique_any;
+
+    {
+        std::vector<int> tmp{1, 2, 3};
+
+        // take a pointer to the orignal data to later verify
+        // that the value was moved, and not copied.
+        auto ptr = tmp.data();
+
+        unique_any a;
+        a = std::move(tmp);
+
+        auto vec = util::any_cast<std::vector<int>>(&a);
+
+        // ensure the value was moved
+        EXPECT_EQ(ptr, vec->data());
+
+        // ensure that the contents of the vector are unchanged
+        std::vector<int> ref{1, 2, 3};
+        EXPECT_EQ(ref, *vec);
+    }
+    {
+        using T = testing::nocopy<int>;
+
+        T tmp(3);
+
+        T::reset_counts();
+        unique_any a;
+        a = std::move(tmp);
+
+        // the move constructor is called when constructing the
+        // contained object
+        EXPECT_EQ(T::move_ctor_count, 1);
+        EXPECT_EQ(T::move_assign_count, 0);
+
+        T::reset_counts();
+        unique_any b = std::move(a);
+
+        // no move of the underlying type because the swap between
+        // is swapping the pointers to contained objects, not the
+        // objects themselves.
+        EXPECT_EQ(T::move_ctor_count, 0);
+        EXPECT_EQ(T::move_assign_count, 0);
+    }
+}
+
+TEST(unique_any, make_unique_any) {
+    using util::make_unique_any;
+    using util::any_cast;
+
+    {
+        auto a = make_unique_any<int>(42);
+
+        EXPECT_EQ(typeid(int), a.type());
+        EXPECT_EQ(42, any_cast<int>(a));
+    }
+
+    // check casting
+    {
+        auto a = make_unique_any<double>(42u);
+
+        EXPECT_EQ(typeid(double), a.type());
+        EXPECT_EQ(42.0, any_cast<double>(a));
+    }
+
+    // check forwarding of parameters to constructor
+    {
+        // create a string from const char*
+        auto as = make_unique_any<std::string>("hello");
+
+        EXPECT_EQ(any_cast<std::string>(as), std::string("hello"));
+
+        // test forwarding of 0 size parameter list
+        struct X {
+            int value;
+            X(): value(42) {}
+        };
+        auto ai = make_unique_any<X>();
+        EXPECT_EQ(any_cast<X>(ai).value, 42);
+
+        // test forwarding of 2 size parameter list
+        auto av = make_unique_any<std::vector<int>>(3, 2);
+        EXPECT_EQ(any_cast<std::vector<int>&>(av), (std::vector<int>{2, 2, 2}));
+    }
+
+    // test that we make_unique_any correctly forwards rvalue arguments to the constructor
+    // of the contained object.
+    {
+        std::vector<int> tmp{1, 2, 3};
+
+        // take a pointer to the orignal data to later verify
+        // that the value was moved, and not copied.
+        auto ptr = tmp.data();
+
+        auto a = make_unique_any<std::vector<int>>(std::move(tmp));
+
+        auto vec = any_cast<std::vector<int>>(&a);
+
+        // ensure the value was moved
+        EXPECT_EQ(ptr, vec->data());
+
+        // ensure that the contents of the vector are unchanged
+        std::vector<int> ref{1, 2, 3};
+        EXPECT_EQ(ref, *vec);
+    }
+}
+
+// test that unique_any plays nicely with STL containers
+TEST(unique_any, stdvector)
+{
+    using util::unique_any;
+
+    // push_back
+    {
+        using T = testing::nocopy<std::string>;
+        auto get = [](const unique_any& v) {return util::any_cast<const T&>(v).value;};
+
+        std::vector<unique_any> vec;
+        vec.push_back(T("h"));
+        vec.push_back(T("e"));
+        vec.push_back(T("l"));
+        vec.push_back(T("l"));
+        vec.push_back(T("o"));
+
+        std::string s;
+        for (auto& v: vec) s += get(v);
+        EXPECT_EQ(s, "hello");
+
+        s.clear();
+        vec.erase(std::begin(vec)+1);
+        vec.erase(std::begin(vec)+1);
+        vec.erase(std::begin(vec)+1);
+        for (auto& v: vec) s += get(v);
+        EXPECT_EQ(s, "ho");
+    }
+
+    // sort
+    {
+        auto get = [](const unique_any& v) {return util::any_cast<int>(v);};
+        int n = 10;
+        std::vector<unique_any> vec;
+
+        // fill the vector with values in descending order:
+        //  [n-1, n-2, ..., 1, 0]
+        for (auto i: util::make_span(0, n)) {
+            vec.emplace_back(n-i-1);
+        }
+        // sort to ascending order
+        util::sort_by(vec, get);
+
+        // verify sort
+        for (auto i: util::make_span(0, n)) {
+            EXPECT_EQ(i, get(vec[i]));
+        }
+    }
+
+    // std::reverse with non-copyable type
+    {
+        using T = testing::nocopy<int>;
+        auto get = [](const unique_any& v) {return util::any_cast<const T&>(v).value;};
+        int n = 10;
+        std::vector<unique_any> vec;
+
+        // fill the vector with values in descending order:
+        //  [n-1, n-2, ..., 1, 0]
+        for (auto i: util::make_span(0, n)) {
+            vec.emplace_back(T(n-i-1));
+        }
+
+        // sort to ascending order by reversing the vector, which is sorted in
+        // descending order.
+        std::reverse(vec.begin(), vec.end());
+
+        // verify sort
+        for (auto i: util::make_span(0, n)) {
+            EXPECT_EQ(i, get(vec[i]));
+        }
+    }
+}