diff --git a/src/util/any.hpp b/src/util/any.hpp new file mode 100644 index 0000000000000000000000000000000000000000..50b29ec78eda6afbd853cf356d4a235b3ae0ffd9 --- /dev/null +++ b/src/util/any.hpp @@ -0,0 +1,234 @@ +#pragma once + +#include <memory> +#include <typeinfo> +#include <type_traits> + +#include <util/meta.hpp> + +// Partial implementation of std::any from C++17 standard. +// http://en.cppreference.com/w/cpp/utility/any +// +// Implements a standard-compliant subset of the full interface. +// +// - Does not avoid dynamic allocation of small objects. +// - Does not implement the in_place_type<T> constructors from the standard. +// - Does not implement the emplace modifier from the standard. + +namespace nest { +namespace mc { +namespace util { + +// Defines a type of object to be thrown by the value-returning forms of +// util::any_cast on failure. +// http://en.cppreference.com/w/cpp/utility/any/bad_any_cast +class bad_any_cast: public std::bad_cast { +public: + const char* what() const noexcept override { + return "bad any cast"; + } +}; + +class any { +public: + constexpr any() = default; + + any(const any& other): state_(other.state_->copy()) {} + + any(any&& other) noexcept { + std::swap(other.state_, state_); + } + + template < + typename T, + typename = typename util::enable_if_t<!std::is_same<util::decay_t<T>, any>::value> + > + any(T&& other) { + using contained_type = util::decay_t<T>; + static_assert(std::is_copy_constructible<contained_type>::value, + "Type of contained object stored in any must satisfy the CopyConstructible requirements."); + + state_.reset(new model<contained_type>(std::forward<T>(other))); + } + + any& operator=(const any& other) { + state_.reset(other.state_->copy()); + return *this; + } + + any& operator=(any&& other) noexcept { + swap(other); + return *this; + } + + template < + typename T, + typename = typename util::enable_if_t<!std::is_same<util::decay_t<T>, any>::value> + > + any& operator=(T&& other) { + using contained_type = util::decay_t<T>; + + static_assert(std::is_copy_constructible<contained_type>::value, + "Type of contained object stored in any must satisfy the CopyConstructible requirements."); + + state_.reset(new model<contained_type>(std::forward<T>(other))); + return *this; + } + + void reset() noexcept { + state_.reset(nullptr); + } + + void swap(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 interface* copy() = 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)) {} + + 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; + }; + + std::unique_ptr<interface> state_; + +protected: + + template <typename T> + friend const T* any_cast(const any* operand); + + template <typename T> + friend T* any_cast(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()); + } +}; + +namespace impl { + +template <typename T> +using any_cast_remove_qual = typename + std::remove_cv<typename std::remove_reference<T>::type>::type; + +} // namespace impl + +// 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 any* operand) { + if (operand && operand->type()==typeid(T)) { + return operand->unsafe_cast<T>(); + } + return nullptr; +} + +template<class T> +T* any_cast(any* operand) { + if (operand && operand->type()==typeid(T)) { + return operand->unsafe_cast<T>(); + } + return nullptr; +} + +template<class T> +T any_cast(const 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(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(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. +// +// This does not exactly follow the standard, which states that +// make_any is equivalent to +// return std::any(std::in_place_type<T>, std::forward<Args>(args)...); +// i.e. that the contained object should be constructed in place, whereas +// this implementation constructs the object, then moves it into the +// contained object. +// FIXME: rewrite with in_place_type when available. +template <class T, class... Args> +any make_any(Args&&... args) { + return any(T(std::forward<Args>(args) ...)); +} + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/tests/global_communication/test_domain_decomposition.cpp b/tests/global_communication/test_domain_decomposition.cpp index ad0048f0cffc529faa49d2d26cc60f66da43b36b..bd6eb35c3d84fe9694a794e6c8093af91436ee73 100644 --- a/tests/global_communication/test_domain_decomposition.cpp +++ b/tests/global_communication/test_domain_decomposition.cpp @@ -13,15 +13,15 @@ using namespace nest::mc; using communicator_type = communication::communicator<communication::global_policy>; -static bool is_dry_run() { +inline bool is_dry_run() { return communication::global_policy::kind() == communication::global_policy_kind::dryrun; } TEST(domain_decomp, basic) { +/* using policy = communication::global_policy; -/* const auto num_domains = policy::size(); const auto rank = policy::id(); */ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index ecae57a183301a035d4d0b4d117d2594b0eaff31..6a5c151ad2d06519192d637d379e2c1f009bd78f 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -31,6 +31,7 @@ set(TEST_CUDA_SOURCES set(TEST_SOURCES # unit tests test_algorithms.cpp + test_any.cpp test_backend.cpp test_double_buffer.cpp test_cell.cpp diff --git a/tests/unit/test_any.cpp b/tests/unit/test_any.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2564bf4ce4f892a71dae1146f29ba85b67fb71c8 --- /dev/null +++ b/tests/unit/test_any.cpp @@ -0,0 +1,309 @@ +#include "../gtest.h" +#include "common.hpp" + +#include <iostream> + +#include <util/any.hpp> + +using namespace nest::mc; + +TEST(any, copy_construction) { + util::any any_int(2); + EXPECT_EQ(any_int.type(), typeid(int)); + + util::any any_float(2.0f); + EXPECT_EQ(any_float.type(), typeid(float)); + + std::string str = "hello"; + util::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(any, move_construction) { + moveable m; + + util::any copied(m); + 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); + EXPECT_EQ(cref.moves, 0); + EXPECT_EQ(cref.copies, 1); + + const auto& mref = *util::any_cast<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::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); + 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(any, type) { + using util::any; + + any anyi(42); + any anys(std::string("hello")); + any anyv(std::vector<int>{1, 2, 3}); + 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(any, swap) { + using util::any; + using util::any_cast; + + any any1(42); // integer + 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(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. +/* +TEST(any, not_copy_constructable) { + util::any a(testing::nocopy<int>(3)); + + testing::nocopy<int> x(3); + util::any b(std::move(x)); +} +*/ + +// test any_cast(any*) +// - these have different behavior to any_cast on reference types +// - are used by the any_cast on refernce types +TEST(any, any_cast_ptr) { + + // test that valid pointers are returned for int and std::string types + + util::any ai(42); + auto ptr_i = util::any_cast<int>(&ai); + EXPECT_EQ(*ptr_i, 42); + + util::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); + util::any empty; + EXPECT_EQ(util::any_cast<int>(&empty), nullptr); + EXPECT_EQ(util::any_cast<int>((util::any*)nullptr), nullptr); + + // Check that constness of the returned pointer matches that the input. + { + 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*"); + } + { + 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*"); + } +} + +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); +} + +// test any_cast(any&&) +TEST(any, any_cast_rvalue) { + auto moved = util::any_cast<moveable>(util::any(moveable())); + EXPECT_EQ(moved.moves, 2); + EXPECT_EQ(moved.copies, 0); +} + +TEST(any, std_swap) { + util::any a1(42); + util::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=(const any&) +TEST(any, assignment_from_lvalue) { + using std::string; + + auto str1 = string("one"); + auto str2 = string("two"); + util::any a(str1); + + util::any b; + b = a; // copy assignment + + // verify that b contains value stored in a + EXPECT_EQ(str1, util::any_cast<string>(b)); + + // change the value stored in b + *util::any_cast<string>(&b) = str2; + + // verify that a is unchanged and that b holds new value + EXPECT_EQ(str1, util::any_cast<string>(a)); + EXPECT_EQ(str2, util::any_cast<string>(b)); +} + +// test operator=(any&&) +TEST(any, assignment_from_rvalue) { + using std::string; + + auto str1 = string("one"); + auto str2 = string("two"); + util::any a(str1); + + util::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(any, assignment_from_value) { + 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(); + + util::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); +} + +TEST(any, make_any) { + using util::make_any; + using util::any_cast; + + { + auto a = make_any<int>(42); + + EXPECT_EQ(typeid(int), a.type()); + EXPECT_EQ(42, any_cast<int>(a)); + } + + // check casting + { + auto a = make_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 a = make_any<std::string>("hello"); + + EXPECT_EQ(any_cast<std::string>(a), std::string("hello")); + } + + // test that we make_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_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); + } + +}