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])); + } + } +}