From: Adam C. Emerson Date: Fri, 2 Feb 2018 18:57:08 +0000 (-0500) Subject: include/any.h: Type Erasure for the uncopyable X-Git-Tag: v13.0.2~151^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f57772ff2af7d3a73c4e72a21c85ee450174f62f;p=ceph.git include/any.h: Type Erasure for the uncopyable std:any is nice and all, but they don't support uncopyable objects. This is annoying to us, since we have a perfect use case for std::any in the associated object map, but almost everything we want to put into it is uncopyable due to having a mutex or something. Enter ceph::immobile_any<>. A zero overhead type erasure that cannot be moved or copied (you stick it in a container and it stays there) of fixed size that does no allocation. Also unique_any and shared_any, because occasionally move and copy semantics are useful but you still want to be able to store an arbitrary type. Signed-off-by: Adam C. Emerson --- diff --git a/src/include/any.h b/src/include/any.h new file mode 100644 index 000000000000..da59c88f482d --- /dev/null +++ b/src/include/any.h @@ -0,0 +1,704 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Adam C. Emerson + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#ifndef INCLUDE_STATIC_ANY +#define INCLUDE_STATIC_ANY + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ceph { + +namespace _any { + +// Shared Functionality +// -------------------- +// +// Common implementation details. Most functionality is here. We +// assume that destructors do not throw. Some of them might and +// they'll invoke terminate and that's fine. +// +// We are using the Curiously Recurring Template Pattern! We require +// that all classes inheriting from us provide: +// +// - `static constexpr size_t capacity`: Maximum capacity. No object +// larger than this may be +// stored. `dynamic` for dynamic. +// - `void* ptr() const noexcept`: returns a pointer to storage. +// (`alloc_storage` must have been called. +// `free_storage` must not have been called +// since.) +// - `void* alloc_storage(const std::size_t)`: allocate storage +// - `void free_storage() noexcept`: free storage. Must be idempotent. +// +// We provide most of the public interface, as well as the operator function, +// cast_helper, and the type() call. + +// Set `capacity` to this value to indicate that there is no fixed +// capacity. +// +inline constexpr std::size_t dynamic = ~0; + +// Driver Function +// --------------- +// +// The usual type-erasure control function trick. This one is simpler +// than usual since we punt on moving and copying. We could dispense +// with this and just store a deleter and a pointer to a typeinfo, but +// that would be twice the space. +// +// Moved out here so the type of `func_t` isn't dependent on the +// enclosing class. +// +enum class op { type, destroy }; +template +inline void op_func(const op o, void* p) noexcept { + static const std::type_info& type = typeid(T); + switch (o) { + case op::type: + *(reinterpret_cast(p)) = &type; + break; + case op::destroy: + reinterpret_cast(p)->~T(); + break; + } +} +using func_t = void (*)(const op, void* p) noexcept; + +// The base class +// -------------- +// +// The `storage_t` parameter gives the type of the value that manages +// storage and allocation. We use it to create a protected data member +// (named `storage`). This allows us to sidestep the problem in +// initialization order where, where exposed constructors were using +// trying to allocate or free storage *before* the data members of the +// derived class were initialized. +// +// Making storage_t a member type of the derived class won't work, due +// to C++'s rules for nested types being *horrible*. Just downright +// *horrible*. +// +template +class base { + // Make definitions from our superclass visible + // -------------------------------------------- + // + // And check that they fit the requirements. At least those that are + // statically checkable. + // + static constexpr std::size_t capacity = D::capacity; + + void* ptr() const noexcept { + static_assert( + noexcept(static_cast(this)->ptr()) && + std::is_same_v(this)->ptr()), void*>, + "‘void* ptr() const noexcept’ missing from superclass"); + return static_cast(this)->ptr(); + } + + void* alloc_storage(const std::size_t z) { + static_assert( + std::is_same_v(this)->alloc_storage(z)), void*>, + "‘void* alloc_storage(const size_t)’ missing from superclass."); + return static_cast(this)->alloc_storage(z); + } + + void free_storage() noexcept { + static_assert( + noexcept(static_cast(this)->free_storage()) && + std::is_void_v(this)->free_storage())>, + "‘void free_storage() noexcept’ missing from superclass."); + static_cast(this)->free_storage(); + } + + + // Pile O' Templates + // ----------------- + // + // These are just verbose and better typed once than twice. They're + // used for SFINAE and declaring noexcept. + // + template + struct is_in_place_type_helper : std::false_type {}; + template + struct is_in_place_type_helper> : std::true_type {}; + + template + static constexpr bool is_in_place_type_v = + is_in_place_type_helper>::value; + + // SFINAE condition for value initialized + // constructors/assigners. This is analogous to the standard's + // requirement that this overload only participate in overload + // resolution if std::decay_t is not the same type as the + // any-type, nor a specialization of std::in_place_type_t + // + template + using value_condition_t = std::enable_if_t< + !std::is_same_v, D> && + !is_in_place_type_v>>; + + // This `noexcept` condition for value construction lets + // `immobile_any`'s value constructor/assigner be noexcept, so long + // as the type's copy or move constructor cooperates. + // + template + static constexpr bool value_noexcept_v = + std::is_nothrow_constructible_v, T> && capacity != dynamic; + + // SFINAE condition for in-place constructors/assigners + // + template + using in_place_condition_t = std::enable_if_t, Args...>>; + + // Analogous to the above. Give noexcept to immobile_any::emplace + // when possible. + // + template + static constexpr bool in_place_noexcept_v = + std::is_nothrow_constructible_v, Args...> && + capacity != dynamic; + +private: + + // Functionality! + // -------------- + + // The driver function for the currently stored object. Whether this + // is null is the canonical way to know whether an instance has a + // value. + // + func_t func = nullptr; + + // Construct an object within ourselves. As you can see we give the + // weak exception safety guarantee. + // + template + std::decay_t& construct(Args&& ...args) { + using Td = std::decay_t; + static_assert(capacity == dynamic || sizeof(Td) <= capacity, + "Supplied type is too large for this specialization."); + try { + func = &op_func; + return *new (reinterpret_cast(alloc_storage(sizeof(Td)))) + Td(std::forward(args)...); + } catch (...) { + reset(); + throw; + } + } + +protected: + + // We hold the storage, even if the superclass class manipulates it, + // so that its default initialization comes soon enough for us to + // use it in our constructors. + // + storage_t storage; + +public: + + base() noexcept = default; + ~base() noexcept { + reset(); + } + +protected: + // Since some of our derived classes /can/ be copied or moved. + // + base(const base& rhs) noexcept : func(rhs.func) { + if constexpr (std::is_copy_assignable_v) { + storage = rhs.storage; + } + } + base& operator =(const base& rhs) noexcept { + reset(); + func = rhs.func; + if constexpr (std::is_copy_assignable_v) { + storage = rhs.storage; + } + return *this; + } + + base(base&& rhs) noexcept : func(std::move(rhs.func)) { + if constexpr (std::is_move_assignable_v) { + storage = std::move(rhs.storage); + } + rhs.func = nullptr; + } + base& operator =(base&& rhs) noexcept { + reset(); + func = rhs.func; + if constexpr (std::is_move_assignable_v) { + storage = std::move(rhs.storage); + } + rhs.func = nullptr; + return *this; + } + +public: + + // Value construct/assign + // ---------------------- + // + template> + base(T&& t) noexcept(value_noexcept_v) { + construct(std::forward(t)); + } + + // On exception, *this is set to empty. + // + template> + base& operator =(T&& t) noexcept(value_noexcept_v) { + reset(); + construct(std::forward(t)); + return *this; + } + + // In-place construct/assign + // ------------------------- + // + // I really hate the way the C++ standard library treats references + // as if they were stepchildren in a Charles Dickens novel. I am + // quite upset that std::optional lacks a specialization for + // references. There's no legitimate reason for it. The whole + // 're-seat or refuse' debate is simply a canard. The optional is + // effectively a container, so of course it can be emptied or + // reassigned. No, pointers are not an acceptable substitute. A + // pointer gives an address in memory which may be null and which + // may represent an object or may a location in which an object is + // to be created. An optional reference, on the other hand, is a + // reference to an initialized, live object or /empty/. This is an + // obvious difference that should be communicable to any programmer + // reading the code through the type system. + // + // `std::any`, even in the case of in-place construction, + // only stores the decayed type. I suspect this was to get around + // the question of whether, for a std::any holding a T&, + // std::any_cast should return a copy or throw + // std::bad_any_cast. + // + // I think the appropriate response in that case would be to make a + // copy if the type supports it and fail otherwise. Once a concrete + // type is known the problem solves itself. + // + // If one were inclined, one could easily load the driver function + // with a heavy subset of the type traits (those that depend only on + // the type in question) and simply /ask/ whether it's a reference. + // + // At the moment, I'm maintaining compatibility with the standard + // library except for copy/move semantics. + // + template> + base(std::in_place_type_t, + Args&& ...args) noexcept(in_place_noexcept_v) { + construct(std::forward(args)...); + } + + // On exception, *this is set to empty. + // + template> + std::decay_t& emplace(Args&& ...args) noexcept(in_place_noexcept_v< + T, Args...>) { + reset(); + return construct(std::forward(args)...); + } + + template, + Args...>> + base(std::in_place_type_t, + std::initializer_list i, + Args&& ...args) noexcept(in_place_noexcept_v, + Args...>) { + construct(i, std::forward(args)...); + } + + // On exception, *this is set to empty. + // + template, + Args...>> + std::decay_t& emplace(std::initializer_list i, + Args&& ...args) noexcept(in_place_noexcept_v, + Args...>) { + reset(); + return construct(i,std::forward(args)...); + } + + // Empty ourselves, using the subclass to free any storage. + // + void reset() noexcept { + if (has_value()) { + func(op::destroy, ptr()); + func = nullptr; + } + free_storage(); + } + + template>> + void swap(base& rhs) { + using std::swap; + swap(func, rhs.func); + swap(storage, rhs.storage); + } + + // All other functions should use this function to test emptiness + // rather than examining `func` directly. + // + bool has_value() const noexcept { + return !!func; + } + + // Returns the type of the value stored, if any. + // + const std::type_info& type() const noexcept { + if (has_value()) { + const std::type_info* t; + func(op::type, reinterpret_cast(&t)); + return *t; + } else { + return typeid(void); + } + } + + template + friend inline void* cast_helper(const base& b) noexcept; +}; + +// Function used by all `any_cast` functions +// +// Returns a void* to the contents if they exist and match the +// requested type, otherwise `nullptr`. +// +template +inline void* cast_helper(const base& b) noexcept { + if (b.func && ((&op_func == b.func) || + (b.type() == typeid(T)))) { + return b.ptr(); + } else { + return nullptr; + } +} +} + +// `any_cast` +// ========== +// +// Just the usual gamut of `any_cast` overloads. These get a bit +// repetitive and it would be nice to think of a way to collapse them +// down a bit. +// + +// The pointer pair! +// +template +inline T* any_cast(_any::base* a) noexcept { + if (a) { + return static_cast(_any::cast_helper>(*a)); + } + return nullptr; +} + +template +inline const T* any_cast(const _any::base* a) noexcept { + if (a) { + return static_cast(_any::cast_helper>(*a)); + } + return nullptr; +} + +// While we disallow copying the immobile any itself, we can allow +// anything with an extracted value that the type supports. +// +template +inline T any_cast(_any::base& a) { + static_assert(std::is_reference_v || + std::is_copy_constructible_v, + "The supplied type must be either a reference or " + "copy constructible."); + auto p = any_cast>(&a); + if (p) { + return static_cast(*p); + } + throw std::bad_any_cast(); +} + +template +inline T any_cast(const _any::base& a) { + static_assert(std::is_reference_v || + std::is_copy_constructible_v, + "The supplied type must be either a reference or " + "copy constructible."); + auto p = any_cast>(&a); + if (p) { + return static_cast(*p); + } + throw std::bad_any_cast(); +} + +template +inline std::enable_if_t<(std::is_move_constructible_v || + std::is_copy_constructible_v) && + !std::is_rvalue_reference_v, T> +any_cast(_any::base&& a) { + auto p = any_cast>(&a); + if (p) { + return std::move((*p)); + } + throw std::bad_any_cast(); +} + +template +inline std::enable_if_t, T> +any_cast(_any::base&& a) { + auto p = any_cast>(&a); + if (p) { + return static_cast(*p); + } + throw std::bad_any_cast(); +} + +// `immobile_any` +// ============== +// +// Sometimes, uncopyable objects exist and I want to do things with +// them. The C++ standard library is really quite keen on insisting +// things be copyable before it deigns to work. I find this annoying. +// +// Also, the allocator, while useful, is really not considerate of +// other people's time. Every time we go to visit it, it takes us +// quite an awfully long time to get away again. As such, I've been +// trying to avoid its company whenever it is convenient and seemly. +// +// We accept any type that will fit in the declared capacity. You may +// store types with throwing destructors, but terminate will be +// invoked when they throw. +// +template +class immobile_any : public _any::base, + std::aligned_storage_t> { + using base = _any::base, std::aligned_storage_t>; + friend base; + + using _any::base, std::aligned_storage_t>::storage; + + // Superclass requirements! + // ------------------------ + // + // Simple as anything. We have a buffer of fixed size and return the + // pointer to it when asked. + // + static constexpr std::size_t capacity = S; + void* ptr() const noexcept { + return const_cast(static_cast(&storage)); + } + void* alloc_storage(std::size_t) noexcept { + return ptr(); + } + void free_storage() noexcept {} + + static_assert(capacity != _any::dynamic, + "That is not a valid size for an immobile_any."); + +public: + + immobile_any() noexcept = default; + + immobile_any(const immobile_any&) = delete; + immobile_any& operator =(const immobile_any&) = delete; + immobile_any(immobile_any&&) = delete; + immobile_any& operator =(immobile_any&&) = delete; + + using base::base; + using base::operator =; + + void swap(immobile_any&) = delete; +}; + +template +inline immobile_any make_immobile_any(Args&& ...args) { + return immobile_any(std::in_place_type, std::forward(args)...); +} + +template +inline immobile_any make_immobile_any(std::initializer_list i, Args&& ...args) { + return immobile_any(std::in_place_type, i, std::forward(args)...); +} + +// `unique_any` +// ============ +// +// Oh dear. Now we're getting back into allocation. You don't think +// the allocator noticed all those mean things we said about it, do +// you? +// +// Well. Okay, allocator. Sometimes when it's the middle of the night +// and you're writing template code you say things you don't exactly +// mean. If it weren't for you, we wouldn't have any memory to run all +// our programs in at all. Really, I'm just being considerate of +// *your* needs, trying to avoid having to run to you every time we +// instantiate a type, making a few that can be self-sufficient…uh… +// +// **Anyway**, this is movable but not copyable, as you should expect +// from anything with ‘unique’ in the name. +// +class unique_any : public _any::base> { + using base = _any::base>; + friend base; + + using base::storage; + + // Superclass requirements + // ----------------------- + // + // Our storage is a single chunk of RAM owned by a + // `std::unique_ptr`. + // + static constexpr std::size_t capacity = _any::dynamic; + void* ptr() const noexcept { + return static_cast(storage.get()); + return nullptr; + } + + void* alloc_storage(const std::size_t z) { + storage.reset(new std::byte[z]); + return ptr(); + } + + void free_storage() noexcept { + storage.reset(); + } + +public: + + unique_any() noexcept = default; + ~unique_any() noexcept = default; + + unique_any(const unique_any&) = delete; + unique_any& operator =(const unique_any&) = delete; + + // We can rely on the behavior of `unique_ptr` and the base class to + // give us a default move constructor that does the right thing. + // + unique_any(unique_any&& rhs) noexcept = default; + unique_any& operator =(unique_any&& rhs) = default; + + using base::base; + using base::operator =; +}; + +inline void swap(unique_any& lhs, unique_any& rhs) noexcept { + lhs.swap(rhs); +} + +template +inline unique_any make_unique_any(Args&& ...args) { + return unique_any(std::in_place_type, std::forward(args)...); +} + +template +inline unique_any make_unique_any(std::initializer_list i, Args&& ...args) { + return unique_any(std::in_place_type, i, std::forward(args)...); +} + +// `shared_any` +// ============ +// +// Once more with feeling! +// +// This is both copyable *and* movable. In case you need that sort of +// thing. It seemed a reasonable completion. +// +class shared_any : public _any::base> { + using base = _any::base>; + friend base; + + using base::storage; + + // Superclass requirements + // ----------------------- + // + // Our storage is a single chunk of RAM allocated from the + // heap. This time it's owned by a `boost::shared_ptr` so we can use + // `boost::make_shared_noinit`. (This lets us get the optimization + // that allocates array and control block in one without wasting + // time on `memset`.) + // + static constexpr std::size_t capacity = _any::dynamic; + void* ptr() const noexcept { + return static_cast(storage.get()); + } + + void* alloc_storage(std::size_t n) { + storage = boost::make_shared_noinit(n); + return ptr(); + } + + void free_storage() noexcept { + storage.reset(); + } + +public: + + shared_any() noexcept = default; + ~shared_any() noexcept = default; + + shared_any(const shared_any& rhs) noexcept = default; + shared_any& operator =(const shared_any&) noexcept = default; + + shared_any(shared_any&& rhs) noexcept = default; + shared_any& operator =(shared_any&& rhs) noexcept = default; + + using base::base; + using base::operator =; +}; + +inline void swap(shared_any& lhs, shared_any& rhs) noexcept { + lhs.swap(rhs); +} + +template +inline shared_any make_shared_any(Args&& ...args) { + return shared_any(std::in_place_type, std::forward(args)...); +} + +template +inline shared_any make_shared_any(std::initializer_list i, Args&& ...args) { + return shared_any(std::in_place_type, i, std::forward(args)...); +} +} + +#endif // INCLUDE_STATIC_ANY diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 3cbfe04fa4da..7f6a68ce979f 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -921,5 +921,9 @@ add_executable(unittest_pageset test_pageset.cc) add_ceph_unittest(unittest_pageset) target_link_libraries(unittest_pageset global) -#make check ends here +# unittest_any_ +add_executable(unittest_any test_any.cc) +add_ceph_unittest(unittest_any) +target_link_libraries(unittest_any) +#make check ends here diff --git a/src/test/test_any.cc b/src/test/test_any.cc new file mode 100644 index 000000000000..03807da05f79 --- /dev/null +++ b/src/test/test_any.cc @@ -0,0 +1,812 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Adam C. Emerson + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License version 2, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include + +#include "gtest/gtest.h" + +#include "include/any.h" + +using std::optional; +using std::bad_any_cast; + +using ceph::immobile_any; +using ceph::unique_any; +using ceph::shared_any; + +using ceph::make_immobile_any; +using ceph::make_unique_any; +using ceph::make_shared_any; + +using ceph::any_cast; +using std::swap; + +template +static void test_empty() { + A a; + EXPECT_FALSE(a.has_value()); + EXPECT_EQ(typeid(void), a.type()); + a.reset(); + EXPECT_FALSE(a.has_value()); + EXPECT_EQ(typeid(void), a.type()); + +} + +TEST(Empty, Immobile) { + static_assert(std::is_nothrow_default_constructible_v>); + test_empty>(); +} + +TEST(Empty, Unique) { + static_assert(std::is_nothrow_default_constructible_v); + test_empty(); +} + +TEST(Empty, Shared) { + static_assert(std::is_nothrow_default_constructible_v); + test_empty(); +} + +struct cmd_tattler { + static thread_local bool copied; + static thread_local bool moved; + static thread_local bool destructed; + + static void reset() { + copied = false; + moved = false; + destructed = false; + } + + cmd_tattler() noexcept = default; + ~cmd_tattler() noexcept { + if (destructed) { + std::terminate(); + } + destructed = true; + } + cmd_tattler(const cmd_tattler&) noexcept { + if (copied) { + std::terminate(); + } + copied = true; + } + cmd_tattler& operator =(const cmd_tattler&) noexcept { + if (copied) { + std::terminate(); + } + copied = true; + return *this; + } + + cmd_tattler(cmd_tattler&&) noexcept { + if (moved) { + std::terminate(); + } + moved = true; + } + cmd_tattler& operator =(cmd_tattler&&) noexcept { + if (moved) { + std::terminate(); + } + moved = true; + return *this; + } +}; + +thread_local bool cmd_tattler::copied = false; +thread_local bool cmd_tattler::moved = false; +thread_local bool cmd_tattler::destructed = false; + +struct not_noexcept { + not_noexcept() = default; + + not_noexcept(const not_noexcept&) noexcept(false) { + } + not_noexcept& operator =(const not_noexcept&) noexcept(false) { + return *this; + } + + not_noexcept(not_noexcept&&) noexcept(false) { + } + not_noexcept& operator =(not_noexcept&&) noexcept(false) { + return *this; + } + + template + not_noexcept(Args&& ...) noexcept(false) { + } + + template + not_noexcept(std::initializer_list, Args&& ...) noexcept(false) { + } +}; + +template +static void test_value_CMD() { + { + cmd_tattler::reset(); + cmd_tattler c; + A a(c); + EXPECT_TRUE(cmd_tattler::copied); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + a.reset(); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_FALSE(a.has_value()); + EXPECT_EQ(typeid(void), a.type()); + + cmd_tattler::reset(); + a = c; + EXPECT_TRUE(cmd_tattler::copied); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + + cmd_tattler::reset(); + a = c; + EXPECT_TRUE(cmd_tattler::copied); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + cmd_tattler::reset(); + a.reset(); + EXPECT_TRUE(cmd_tattler::destructed); + cmd_tattler::reset(); + } + { + cmd_tattler::reset(); + cmd_tattler c; + A a(std::move(c)); + EXPECT_TRUE(cmd_tattler::moved); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + a.reset(); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_FALSE(a.has_value()); + EXPECT_EQ(typeid(void), a.type()); + + cmd_tattler::reset(); + a = std::move(c); + EXPECT_TRUE(cmd_tattler::moved); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + + cmd_tattler::reset(); + a = std::move(c); + EXPECT_TRUE(cmd_tattler::moved); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + cmd_tattler::reset(); + a.reset(); + EXPECT_TRUE(cmd_tattler::destructed); + cmd_tattler::reset(); + } + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + EXPECT_TRUE(cmd_tattler::moved); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + + cmd_tattler::reset(); + a.reset(); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_FALSE(a.has_value()); + EXPECT_EQ(typeid(void), a.type()); + + cmd_tattler::reset(); + a = cmd_tattler{}; + EXPECT_TRUE(cmd_tattler::moved); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(cmd_tattler), a.type()); + cmd_tattler::reset(); + a.reset(); + EXPECT_TRUE(cmd_tattler::destructed); + cmd_tattler::reset(); + } +} + + + +TEST(Value_CMD, Immobile) { + static_assert(std::is_nothrow_constructible_v< + immobile_any<1024>, const cmd_tattler&>); + static_assert(std::is_nothrow_assignable_v< + immobile_any<1024>, const cmd_tattler&>); + static_assert(std::is_nothrow_constructible_v< + immobile_any<1024>, cmd_tattler&&>); + static_assert(std::is_nothrow_assignable_v< + immobile_any<1024>, cmd_tattler&&>); + + static_assert(!std::is_nothrow_constructible_v< + immobile_any<1024>, const not_noexcept&>); + static_assert(!std::is_nothrow_assignable_v< + immobile_any<1024>, const not_noexcept&>); + static_assert(!std::is_nothrow_constructible_v< + immobile_any<1024>, not_noexcept&&>); + static_assert(!std::is_nothrow_assignable_v< + immobile_any<1024>, not_noexcept&&>); + + test_value_CMD>(); +} + +TEST(Value_CMD, Unique) { + static_assert(!std::is_nothrow_constructible_v< + unique_any, const cmd_tattler&>); + static_assert(!std::is_nothrow_assignable_v< + unique_any, const cmd_tattler&>); + static_assert(!std::is_nothrow_constructible_v< + unique_any, cmd_tattler&&>); + static_assert(!std::is_nothrow_assignable_v< + unique_any, cmd_tattler&&>); + + static_assert(!std::is_nothrow_constructible_v< + unique_any, const not_noexcept&>); + static_assert(!std::is_nothrow_assignable_v< + unique_any, const not_noexcept&>); + static_assert(!std::is_nothrow_constructible_v< + unique_any, not_noexcept&&>); + static_assert(!std::is_nothrow_assignable_v< + unique_any, not_noexcept&&>); + + test_value_CMD(); +} + +TEST(Value_CMD, Shared) { + static_assert(!std::is_nothrow_constructible_v< + shared_any, const cmd_tattler&>); + static_assert(!std::is_nothrow_assignable_v< + shared_any, const cmd_tattler&>); + static_assert(!std::is_nothrow_constructible_v< + shared_any, cmd_tattler&&>); + static_assert(!std::is_nothrow_assignable_v< + shared_any, cmd_tattler&&>); + + static_assert(!std::is_nothrow_constructible_v< + shared_any, const not_noexcept&>); + static_assert(!std::is_nothrow_assignable_v< + shared_any, const not_noexcept&>); + static_assert(!std::is_nothrow_constructible_v< + shared_any, not_noexcept&&>); + static_assert(!std::is_nothrow_assignable_v< + shared_any, not_noexcept&&>); + + test_value_CMD(); +} + +template +static void test_move() { + { + A a(5); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(int), a.type()); + + A b(std::move(a)); + EXPECT_TRUE(b.has_value()); + EXPECT_EQ(typeid(int), b.type()); + + EXPECT_FALSE(a.has_value()); + EXPECT_EQ(typeid(void), a.type()); + } + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + + A b(5); + EXPECT_TRUE(b.has_value()); + EXPECT_EQ(typeid(int), b.type()); + cmd_tattler::reset(); + + a = std::move(b); + EXPECT_TRUE(cmd_tattler::destructed); + + EXPECT_FALSE(b.has_value()); + EXPECT_EQ(typeid(void), b.type()); + + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(int), a.type()); + } +} + +static_assert(!std::is_move_constructible_v>); +static_assert(!std::is_move_assignable_v>); + +TEST(Move, Unique) { + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + + test_move(); +} + +TEST(Move, Shared) { + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + + test_move(); +} + +template +static void test_copy() { + { + const A a(5); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(int), a.type()); + + A b(a); + EXPECT_TRUE(b.has_value()); + EXPECT_EQ(typeid(int), b.type()); + + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(int), a.type()); + + EXPECT_EQ(any_cast(a), any_cast(b)); + } + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + + const A b(5); + EXPECT_TRUE(b.has_value()); + EXPECT_EQ(typeid(int), b.type()); + cmd_tattler::reset(); + + a = b; + EXPECT_TRUE(cmd_tattler::destructed); + + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(int), a.type()); + + EXPECT_TRUE(b.has_value()); + EXPECT_EQ(typeid(int), b.type()); + + EXPECT_EQ(any_cast(a), any_cast(b)); + } +} + +static_assert(!std::is_copy_constructible_v>); +static_assert(!std::is_copy_assignable_v>); + +static_assert(!std::is_copy_constructible_v); +static_assert(!std::is_copy_assignable_v); + +TEST(Copy, Shared) { + static_assert(std::is_nothrow_copy_constructible_v); + test_copy(); +} + +struct unmoving { + optional a; + + unmoving() noexcept {} + + template + unmoving(Args&& ...args) noexcept + : a(sizeof...(Args)) {} + + template + unmoving(std::initializer_list l) noexcept + : a(-l.size()) {} + + template + unmoving(std::initializer_list l, Args&& ...args) noexcept + : a(-l.size() * sizeof...(Args)) {} + + unmoving(const unmoving&) = delete; + unmoving& operator =(const unmoving&) = delete; + + unmoving(unmoving&&) = delete; + unmoving& operator =(unmoving&&) = delete; +}; + +template +static void test_unmoving_pack_il() { + // Nothing! + { + const A a(std::in_place_type); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_FALSE(any_cast(a).a); + } + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + + cmd_tattler::reset(); + a.template emplace(); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_FALSE(any_cast(a).a); + } + + // Pack! + { + const A a(std::in_place_type, nullptr, 5, 3.1); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(3, *any_cast(a).a); + } + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + + cmd_tattler::reset(); + a.template emplace(nullptr, 5, 3.1); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_EQ(3, *any_cast(a).a); + } + + // List! + { + const A a(std::in_place_type, {true, true, true, true}); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-4, *any_cast(a).a); + } + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + + cmd_tattler::reset(); + a.template emplace({true, true, true, true}); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_EQ(-4, *any_cast(a).a); + } + + // List + pack!! + { + const A a(std::in_place_type, {true, true, true, true}, + nullptr, 5, 3.1); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-12, *any_cast(a).a); + } + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + + cmd_tattler::reset(); + a.template emplace({true, true, true, true}, nullptr, 5, 3.1); + EXPECT_TRUE(cmd_tattler::destructed); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_EQ(-12, *any_cast(a).a); + } +} + +TEST(UmovingPackIl, Immobile) { + static_assert(std::is_nothrow_constructible_v, + std::in_place_type_t>); + static_assert(noexcept(immobile_any<1024>{}.emplace())); + + static_assert(std::is_nothrow_constructible_v, + std::in_place_type_t, std::nullptr_t, int, double>); + static_assert(noexcept(immobile_any<1024>{}.emplace( + nullptr, 5, 3.1))); + + static_assert(std::is_nothrow_constructible_v, + std::in_place_type_t, std::initializer_list>); + static_assert(noexcept(immobile_any<1024>{}.emplace( + {true, true, true, true}))); + + static_assert(std::is_nothrow_constructible_v, + std::in_place_type_t, std::initializer_list, + std::nullptr_t, int, double>); + static_assert(noexcept(immobile_any<1024>{}.emplace( + {true, true, true, true}, nullptr, 5, 3.1))); + + test_unmoving_pack_il>(); +} + +TEST(UmovingPackIl, Unique) { + static_assert(!std::is_nothrow_constructible_v>); + static_assert(!noexcept(unique_any{}.emplace())); + + static_assert(!std::is_nothrow_constructible_v, std::nullptr_t, int, double>); + static_assert(!noexcept(unique_any{}.emplace( + nullptr, 5, 3.1))); + + static_assert(!std::is_nothrow_constructible_v, std::initializer_list>); + static_assert(!noexcept(unique_any{}.emplace( + {true, true, true, true}))); + + static_assert(!std::is_nothrow_constructible_v, std::initializer_list, + std::nullptr_t, int, double>); + static_assert(!noexcept(unique_any{}.emplace( + {true, true, true, true}, nullptr, 5, 3.1))); + + test_unmoving_pack_il(); +} + +TEST(UmovingPackIl, Shared) { + static_assert(!std::is_nothrow_constructible_v>); + static_assert(!noexcept(shared_any{}.emplace())); + + static_assert(!std::is_nothrow_constructible_v, std::nullptr_t, int, double>); + static_assert(!noexcept(shared_any{}.emplace( + nullptr, 5, 3.1))); + + static_assert(!std::is_nothrow_constructible_v, std::initializer_list>); + static_assert(!noexcept(shared_any{}.emplace( + {true, true, true, true}))); + + static_assert(!std::is_nothrow_constructible_v, std::initializer_list, + std::nullptr_t, int, double>); + static_assert(!noexcept(shared_any{}.emplace( + {true, true, true, true}, nullptr, 5, 3.1))); + + test_unmoving_pack_il(); +} + +template +static void test_swap() { + A a(true); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(typeid(bool), a.type()); + ASSERT_EQ(true, any_cast(a)); + + A b(5); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(typeid(int), b.type()); + ASSERT_EQ(5, any_cast(b)); + + a.swap(b); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(int), a.type()); + EXPECT_EQ(5, any_cast(a)); + + EXPECT_TRUE(b.has_value()); + ASSERT_EQ(typeid(bool), b.type()); + ASSERT_EQ(true, any_cast(b)); + + swap(a,b); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(bool), a.type()); + EXPECT_EQ(true, any_cast(a)); + + EXPECT_TRUE(b.has_value()); + EXPECT_EQ(typeid(int), b.type()); + EXPECT_EQ(5, any_cast(b)); +} + +static_assert(!std::is_swappable_v>); + +TEST(Swap, Unique) { + static_assert(std::is_nothrow_swappable_v); + test_swap(); +} + +TEST(Swap, Shared) { + static_assert(std::is_nothrow_swappable_v); + test_swap(); +} + +template +static void test_cast() { + // Empty + { + A a; + EXPECT_EQ(nullptr, any_cast(&a)); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); + EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); + } + + // Constant Empty + { + const A a{}; + EXPECT_EQ(nullptr, any_cast(const_cast(&a))); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + } + + // Filled! + { + A a(true); + EXPECT_TRUE(*any_cast(&a)); + EXPECT_EQ(nullptr, any_cast(&a)); + + EXPECT_TRUE(any_cast(a)); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + + EXPECT_TRUE(any_cast(a)); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + + EXPECT_TRUE(any_cast(std::move(a))); + EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); + + EXPECT_TRUE(any_cast(std::move(a))); + EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); + } + + // Constant filled + { + const A a(true); + EXPECT_TRUE(*any_cast(&a)); + EXPECT_EQ(nullptr, any_cast(&a)); + + EXPECT_TRUE(any_cast(a)); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + + EXPECT_TRUE(any_cast(a)); + EXPECT_THROW({any_cast(a);}, bad_any_cast); + } + + // Move! + { + cmd_tattler::reset(); + A a(cmd_tattler{}); + cmd_tattler::reset(); + + auto q = any_cast(std::move(a)); + EXPECT_TRUE(cmd_tattler::moved); + cmd_tattler::reset(); + a.reset(); + cmd_tattler::reset(); + } + + // Move! Again! + { + cmd_tattler::reset(); + auto q = any_cast(A(std::in_place_type)); + EXPECT_TRUE(cmd_tattler::moved); + cmd_tattler::reset(); + } +} + +TEST(Cast, Immobile) { + test_cast>(); +} + +TEST(Cast, Unique) { + test_cast(); +} + +TEST(Cast, Shared) { + test_cast(); +} + +TEST(Make, Immobile) { + // Nothing! + { + auto a{make_immobile_any()}; + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_FALSE(any_cast(a).a); + } + + // Pack! + { + auto a(make_immobile_any(nullptr, 5, 3.1)); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(3, *any_cast(a).a); + } + + // List! + { + auto a(make_immobile_any({true, true, true, true})); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-4, *any_cast(a).a); + } + + // List + pack!! + { + auto a{make_immobile_any({true, true, true, true}, + nullptr, 5, 3.1)}; + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-12, *any_cast(a).a); + } +} + +TEST(Make, Unique) { + // Nothing! + { + auto a{make_unique_any()}; + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_FALSE(any_cast(a).a); + } + + // Pack! + { + auto a(make_unique_any(nullptr, 5, 3.1)); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(3, *any_cast(a).a); + } + + // List! + { + auto a(make_unique_any({true, true, true, true})); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-4, *any_cast(a).a); + } + + // List + pack!! + { + auto a{make_unique_any({true, true, true, true}, + nullptr, 5, 3.1)}; + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-12, *any_cast(a).a); + } +} + +TEST(Make, Shared) { + // Nothing! + { + auto a{make_shared_any()}; + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_FALSE(any_cast(a).a); + } + + // Pack! + { + auto a(make_shared_any(nullptr, 5, 3.1)); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(3, *any_cast(a).a); + } + + // List! + { + auto a(make_shared_any({true, true, true, true})); + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-4, *any_cast(a).a); + } + + // List + pack!! + { + auto a{make_shared_any({true, true, true, true}, + nullptr, 5, 3.1)}; + EXPECT_TRUE(a.has_value()); + EXPECT_EQ(typeid(unmoving), a.type()); + EXPECT_TRUE(any_cast(a).a); + EXPECT_EQ(-12, *any_cast(a).a); + } +}