diff --git a/doc/corrade-changelog.dox b/doc/corrade-changelog.dox index e96a13b99..e98de88bd 100644 --- a/doc/corrade-changelog.dox +++ b/doc/corrade-changelog.dox @@ -45,7 +45,9 @@ namespace Corrade { @subsubsection corrade-changelog-latest-new-containers Containers library -- New @ref Corrade::Containers::NoCreate tag for constructing instances +- New @ref Containers::Pointer class, a lightweight equivalent to + @ref std::unique_ptr +- New @ref Containers::NoCreate tag for constructing instances equivalent to a moved-from state. This tag was used in Magnum already, moving it here to make it available to a wider set of APIs. - New @ref Containers::optional(Args&&... args) overload for in-place diff --git a/doc/snippets/Containers.cpp b/doc/snippets/Containers.cpp index 6c7dea092..66a04cc24 100644 --- a/doc/snippets/Containers.cpp +++ b/doc/snippets/Containers.cpp @@ -34,6 +34,7 @@ #include "Corrade/Containers/EnumSet.hpp" #include "Corrade/Containers/LinkedList.h" #include "Corrade/Containers/Optional.h" +#include "Corrade/Containers/Pointer.h" #include "Corrade/Containers/ScopedExit.h" #include "Corrade/Containers/StaticArray.h" #include "Corrade/Containers/StridedArrayView.h" @@ -669,4 +670,30 @@ Containers::StridedArrayView view2 = data; static_cast(view2); } +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +{ +/* [pointer] */ +std::string* ptr; + +auto a = Containers::Pointer{ptr}; +auto b = Containers::pointer(ptr); +/* [pointer] */ +} +#if defined(__clang__) || defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +{ +/* [pointer-inplace] */ +auto a = Containers::Pointer{Containers::InPlaceInit, 'a', 'b'}; +auto b = Containers::pointer('a', 'b'); +/* [pointer-inplace] */ +} + } diff --git a/src/Corrade/Containers/CMakeLists.txt b/src/Corrade/Containers/CMakeLists.txt index aa5595f01..100a934d8 100644 --- a/src/Corrade/Containers/CMakeLists.txt +++ b/src/Corrade/Containers/CMakeLists.txt @@ -31,6 +31,7 @@ set(CorradeContainers_HEADERS EnumSet.hpp LinkedList.h Optional.h + Pointer.h ScopedExit.h StaticArray.h StridedArrayView.h diff --git a/src/Corrade/Containers/Containers.h b/src/Corrade/Containers/Containers.h index eb1e31faf..75455492c 100644 --- a/src/Corrade/Containers/Containers.h +++ b/src/Corrade/Containers/Containers.h @@ -49,6 +49,7 @@ template class LinkedList; template> class LinkedListItem; template class Optional; +template class Pointer; #endif }} diff --git a/src/Corrade/Containers/Optional.h b/src/Corrade/Containers/Optional.h index 1ae23fb30..6dadea6ad 100644 --- a/src/Corrade/Containers/Optional.h +++ b/src/Corrade/Containers/Optional.h @@ -68,7 +68,7 @@ constexpr NullOptT NullOpt{NullOptT::Init{}}; Equivalent to `std::optional` from C++17, provides an optional checked storage for object of type @p T. The optional object can be seen as a container of @p T objects with maximal size 1 and can be in two states, either empty or having a -value. +value. A non-allocating counterpart to @ref Pointer. A common use for an optional object is for a return value of function that can fail --- like @ref std::unique_ptr, but without the unnecessary allocation @@ -348,7 +348,7 @@ The following two lines are equivalent: time and you're advised to use the @ref Optional constructor explicitly to avoid surprising behavior. -@see @ref optional(Args&&... args) +@see @ref optional(Args&&... args), @ref pointer(T*) */ template inline Optional::type> optional(T&& value) { return Optional::type>{std::forward(value)}; @@ -368,7 +368,7 @@ The following two lines are equivalent: you're advised to use the @ref Optional constructor explicitly to avoid surprising behavior. -@see @ref optional(T&&) +@see @ref optional(T&&), @ref pointer(Args&&... args) */ template inline Optional optional(Args&&... args) { return Optional{InPlaceInit, std::forward(args)...}; diff --git a/src/Corrade/Containers/Pointer.h b/src/Corrade/Containers/Pointer.h new file mode 100644 index 000000000..ec1696a71 --- /dev/null +++ b/src/Corrade/Containers/Pointer.h @@ -0,0 +1,289 @@ +#ifndef Corrade_Containers_Pointer_h +#define Corrade_Containers_Pointer_h +/* + This file is part of Corrade. + + Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Corrade::Containers::Pointer, function @ref Corrade::Containers::pointer() + */ + +#include +#include /* std::forward() */ + +#include "Corrade/Containers/Tags.h" +#include "Corrade/Utility/Assert.h" +#include "Corrade/Utility/Debug.h" + +namespace Corrade { namespace Containers { + +/** +@brief Lightweight unique pointer + +Equivalent to @ref std::unique_ptr from C++11, provides an owning move-only +wrapper over a pointer of type @p T, calling @cpp delete @ce on it on +destruction. The @ref pointer() convenience function also provides an +equivalent for C++14 @cpp std::make_unique() @ce, but on C++11 as well. Can be +also thought of as a heap-allocated counterpart to @ref Optional. + +Unlike @ref std::unique_ptr, this class does not provide custom deleters, +doesn't work with arrays and doesn't have a @cpp constexpr @ce API. On the +other hand that makes it fairly simple and lightweight. If you need a custom +deleter, use either @ref ScopedExit or the standard @ref std::unique_ptr. For +owning array wrappers use @ref Array, which maintains a size information and +also supports custom deleters. +@see @ref pointer(T*), @ref pointer(Args&&... args) +*/ +template class Pointer { + static_assert(!std::is_array::value, "use Containers::Array for arrays instead"); + + public: + /** + * @brief Default constructor + * + * Creates a @cpp nullptr @ce unique pointer. + * @see @ref operator bool(), @ref reset() + */ + /*implicit*/ Pointer(std::nullptr_t = nullptr) noexcept: _pointer{} {} + + /** + * @brief Construct a pointer by value + * + * Takes ownership of the passed pointer. + * @see @ref operator bool(), @ref operator->() + */ + explicit Pointer(T* pointer) noexcept: _pointer{pointer} {} + + /** + * @brief Construct a pointer in-place + * + * Allocates a new object by passing @p args to its constructor. + * @see @ref operator bool(), @ref operator->() + */ + template explicit Pointer(InPlaceInitT, Args&&... args): _pointer{new T{std::forward(args)...}} {} + + /** @brief Copying is not allowed */ + Pointer(const Pointer&) = delete; + + /** @brief Move constructor */ + Pointer(Pointer&& other) noexcept: _pointer{other._pointer} { + other._pointer = nullptr; + } + + /** @brief Copying is not allowed */ + Pointer& operator=(const Pointer&) = delete; + + /** @brief Move assignment */ + Pointer& operator=(Pointer&& other) noexcept { + std::swap(_pointer, other._pointer); + return *this; + } + + /** + * @brief Equality comparison to a null pointer + * + * Returns @cpp true @ce if the poiner is @cpp nullptr @ce, + * @cpp false @ce otherwise. + * @see @ref operator bool() + */ + bool operator==(std::nullptr_t) const { return !_pointer; } + + /** + * @brief Non-equality comparison to a null pointer + * + * Returns @cpp false @ce if the pointer is @cpp nullptr @ce, + * @cpp false @ce otherwise. + * @see @ref operator bool() + */ + bool operator!=(std::nullptr_t) const { return _pointer; } + + /** + * @brief Destructor + * + * Calls @cpp delete @ce on the stored pointer. + */ + ~Pointer() { delete _pointer; } + + /** + * @brief Whether the pointer is non-null + * + * Returns @cpp false @ce if stored pointer is @cpp nullptr @ce, + * @cpp true @ce otherwise. + */ + explicit operator bool() const { return _pointer; } + + /** + * @brief Underlying pointer value + * + * @see @ref operator bool(), @ref operator->(), @ref release() + */ + T* get() { return _pointer; } + const T* get() const { return _pointer; } /**< @overload */ + + /** + * @brief Access the underlying pointer + * + * Expects that the pointer is not @cpp nullptr @ce. + * @see @ref operator bool(), @ref get(), @ref operator*(), + * @ref release() + */ + T* operator->() { + CORRADE_ASSERT(_pointer, "Containers::Pointer: the pointer is null", nullptr); + return _pointer; + } + + /** @overload */ + const T* operator->() const { + CORRADE_ASSERT(_pointer, "Containers::Pointer: the pointer is null", nullptr); + return _pointer; + } + + /** + * @brief Access the underlying pointer + * + * Expects that the pointer is not @cpp nullptr @ce. + * @see @ref operator bool(), @ref get(), @ref operator->(), + * @ref release() + */ + T& operator*() { + CORRADE_ASSERT(_pointer, "Containers::Pointer: the pointer is null", *_pointer); + return *_pointer; + } + + /** @overload */ + const T& operator*() const { + CORRADE_ASSERT(_pointer, "Containers::Pointer: the pointer is null", *_pointer); + return *_pointer; + } + + /** + * @brief Reset the pointer to a new value + * + * Calls @cpp delete @ce on the previously stored pointer and replaces + * it with @p pointer. + * @see @ref release() + */ + void reset(T* pointer) { + delete _pointer; + _pointer = pointer; + } + + /** + * @brief Emplace a new value + * + * Calls @cpp delete @ce on the previously stored pointer and allocates + * a new object by passing @p args to its constructor. + */ + template T& emplace(Args&&... args) { + delete _pointer; + _pointer = new T{std::forward(args)...}; + return *_pointer; + } + + /** + * @brief Release the pointer ownership + * + * Resets the stored pointer to @cpp nullptr @ce, returning the + * previous value. + * @see @ref get(), @ref reset() + */ + T* release() { + T* const out = _pointer; + _pointer = nullptr; + return out; + } + + private: + T* _pointer; +}; + +/** @relates Pointer +@brief Equality comparison of a null pointer and an unique pointer + +See @ref Pointer::operator==(std::nullptr_t) const for more information. +*/ +template bool operator==(std::nullptr_t, const Pointer& b) { return b == nullptr; } + +/** @relates Pointer +@brief Non-euality comparison of a null pointer and an unique pointer + +See @ref Pointer::operator!=(std::nullptr_t) const for more information. +*/ +template bool operator!=(std::nullptr_t, const Pointer& b) { return b != nullptr; } + +/** @relatesalso Pointer +@brief Make a unique pointer + +Convenience alternative to @ref Pointer::Pointer(T*). The following two +lines are equivalent: + +@snippet Containers.cpp pointer + +@attention Note that for types that are constructible from their own pointer + the call would get ambiguous between this function and + @ref pointer(Args&&... args). Such case is forbidden at compile time in + order to prevent potentially dangerous behavior and you need to explicitly + use the @ref Pointer constructor instead. + +@see @ref pointer(Args&&... args), @ref optional(T&&) +*/ +template inline Pointer pointer(T* pointer) { + static_assert(!std::is_constructible::value, "the type is constructible from its own pointer, which is ambiguous -- explicitly use the constructor instead"); + return Pointer{pointer}; +} + +namespace Implementation { + template struct IsFirstAPointer: std::false_type {}; + template struct IsFirstAPointer: std::true_type {}; +} + +/** @relatesalso Pointer +@brief Make a unique pointer + +Convenience alternative to @ref Pointer::Pointer(InPlaceInitT, Args&&... args). +The following two lines are equivalent: + +@snippet Containers.cpp pointer-inplace + +@attention Note that for types that are constructible from their own pointer + the call would get ambiguous between this function and @ref pointer(T*). + Such case is forbidden at compile time in order to prevent potentially + dangerous behavior and you need to explicitly use the @ref Pointer + constructor instead. + +@see @ref pointer(T*), @ref optional(Args&&... args) +*/ +template inline Pointer pointer(Args&&... args) { + static_assert(!Implementation::IsFirstAPointer::value || !std::is_constructible::value, "attempt to construct a type from its own pointer, which is ambiguous -- explicitly use the constructor instead"); + return Pointer{InPlaceInit, std::forward(args)...}; +} + +/** @debugoperator{Pointer} */ +template Utility::Debug& operator<<(Utility::Debug& debug, const Pointer& value) { + return debug << value.get(); +} + +}} + +#endif diff --git a/src/Corrade/Containers/Test/CMakeLists.txt b/src/Corrade/Containers/Test/CMakeLists.txt index a14b6104a..419626e80 100644 --- a/src/Corrade/Containers/Test/CMakeLists.txt +++ b/src/Corrade/Containers/Test/CMakeLists.txt @@ -28,6 +28,7 @@ corrade_add_test(ContainersArrayViewTest ArrayViewTest.cpp) corrade_add_test(ContainersEnumSetTest EnumSetTest.cpp) corrade_add_test(ContainersLinkedListTest LinkedListTest.cpp) corrade_add_test(ContainersOptionalTest OptionalTest.cpp) +corrade_add_test(ContainersPointerTest PointerTest.cpp) corrade_add_test(ContainersScopedExitTest ScopedExitTest.cpp) corrade_add_test(ContainersStaticArrayTest StaticArrayTest.cpp) corrade_add_test(ContainersStaticArrayViewTest StaticArrayViewTest.cpp) @@ -39,6 +40,7 @@ set_property(TARGET ContainersArrayTest ContainersArrayViewTest ContainersOptionalTest + ContainersPointerTest ContainersStaticArrayViewTest ContainersStridedArrayViewTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") @@ -48,6 +50,7 @@ set_target_properties( ContainersArrayViewTest ContainersEnumSetTest ContainersLinkedListTest + ContainersPointerTest ContainersStaticArrayTest ContainersStaticArrayViewTest ContainersStridedArrayViewTest diff --git a/src/Corrade/Containers/Test/PointerTest.cpp b/src/Corrade/Containers/Test/PointerTest.cpp new file mode 100644 index 000000000..495043d9e --- /dev/null +++ b/src/Corrade/Containers/Test/PointerTest.cpp @@ -0,0 +1,396 @@ +/* + This file is part of Corrade. + + Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "Corrade/Containers/Pointer.h" +#include "Corrade/TestSuite/Tester.h" + +namespace Corrade { namespace Containers { namespace Test { namespace { + +struct PointerTest: TestSuite::Tester { + explicit PointerTest(); + + void resetCounters(); + + void construct(); + void constructDefault(); + void constructNullptr(); + void constructCopy(); + void constructMove(); + void constructMake(); + void constructInPlace(); + void constructInPlaceMake(); + void constructInPlaceMakeAmbiguous(); + + void boolConversion(); + void compareToNullptr(); + + void access(); + void accessInvalid(); + + void reset(); + void emplace(); + void release(); + + void debug(); +}; + +PointerTest::PointerTest() { + addTests({&PointerTest::construct, + &PointerTest::constructDefault, + &PointerTest::constructNullptr}, &PointerTest::resetCounters, &PointerTest::resetCounters); + + addTests({&PointerTest::constructCopy}); + + addTests({&PointerTest::constructMove, + &PointerTest::constructMake, + &PointerTest::constructInPlace, + &PointerTest::constructInPlaceMake}, &PointerTest::resetCounters, &PointerTest::resetCounters); + + addTests({&PointerTest::constructInPlaceMakeAmbiguous, + + &PointerTest::boolConversion, + &PointerTest::compareToNullptr}); + + addTests({&PointerTest::access}, &PointerTest::resetCounters, &PointerTest::resetCounters); + + addTests({&PointerTest::accessInvalid}); + + addTests({&PointerTest::reset, + &PointerTest::emplace, + &PointerTest::release}, &PointerTest::resetCounters, &PointerTest::resetCounters); + + addTests({&PointerTest::debug}); +} + +struct Immovable { + static int constructed; + static int destructed; + + Immovable(const Immovable&) = delete; + Immovable(Immovable&&) = delete; + explicit Immovable(int a) noexcept: a{a} { ++constructed; } + /* To test perfect forwarding in in-place construction */ + explicit Immovable(int a, int&&) noexcept: Immovable{a} {} + ~Immovable() { ++destructed; } + Immovable& operator=(const Immovable&) = delete; + Immovable& operator=(Immovable&&) = delete; + + int a; +}; + +int Immovable::constructed = 0; +int Immovable::destructed = 0; + +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-member-function" +#endif +struct Throwable { + /* These have to be defined and not defaulted so the compiler doesn't put + noexcept on them. But Clang warns that they're unused. */ + explicit Throwable(int) {} + Throwable(const Throwable&) {} + Throwable(Throwable&&) {} + Throwable& operator=(const Throwable&) { return *this; } + Throwable& operator=(Throwable&&) { return *this; } +}; +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif + +void PointerTest::resetCounters() { + Immovable::constructed = Immovable::destructed = 0; +} + +void PointerTest::construct() { + { + Pointer a{new Immovable{42}}; + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 42); + } + + CORRADE_COMPARE(Immovable::constructed, 1); + CORRADE_COMPARE(Immovable::destructed, 1); + + CORRADE_VERIFY((std::is_nothrow_constructible, int*>::value)); + CORRADE_VERIFY(!(std::is_assignable, int*>::value)); +} + +void PointerTest::constructDefault() { + Pointer a; + Pointer b = {}; + CORRADE_VERIFY(!a); + CORRADE_VERIFY(!b); + + CORRADE_COMPARE(Immovable::constructed, 0); + CORRADE_COMPARE(Immovable::destructed, 0); + + CORRADE_VERIFY((std::is_nothrow_constructible>::value)); +} + +void PointerTest::constructNullptr() { + Pointer a{nullptr}; + Pointer b = nullptr; + CORRADE_VERIFY(!a); + CORRADE_VERIFY(!b); + + CORRADE_COMPARE(Immovable::constructed, 0); + CORRADE_COMPARE(Immovable::destructed, 0); + + CORRADE_VERIFY((std::is_nothrow_constructible, std::nullptr_t>::value)); + CORRADE_VERIFY((std::is_nothrow_assignable, std::nullptr_t>::value)); +} + +void PointerTest::constructCopy() { + CORRADE_VERIFY(!std::is_copy_constructible>::value); + CORRADE_VERIFY(!std::is_copy_assignable>::value); +} + +void PointerTest::constructMove() { + { + Pointer a{new Immovable{32}}; + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 32); + + Pointer b = std::move(a); + CORRADE_VERIFY(!a); + CORRADE_VERIFY(b); + CORRADE_COMPARE(b->a, 32); + + Pointer c{new Immovable{56}}; + CORRADE_VERIFY(c); + CORRADE_COMPARE(c->a, 56); + + c = std::move(b); + CORRADE_VERIFY(c); + CORRADE_COMPARE(c->a, 32); + } + + CORRADE_COMPARE(Immovable::constructed, 2); + CORRADE_COMPARE(Immovable::destructed, 2); + + CORRADE_VERIFY(std::is_nothrow_move_constructible>::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable>::value); + CORRADE_VERIFY(std::is_nothrow_move_constructible>::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable>::value); +} + +void PointerTest::constructMake() { + { + auto a = pointer(new Immovable{1337}); + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 1337); + } + + CORRADE_COMPARE(Immovable::constructed, 1); + CORRADE_COMPARE(Immovable::destructed, 1); +} + +void PointerTest::constructInPlace() { + { + /* Using int{} to test perfect forwarding */ + Pointer a{InPlaceInit, -13, int{}}; + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, -13); + } + + CORRADE_COMPARE(Immovable::constructed, 1); + CORRADE_COMPARE(Immovable::destructed, 1); + + /* This is never noexcept since we allocate (duh) */ + CORRADE_VERIFY((std::is_constructible, InPlaceInitT, int>::value)); + CORRADE_VERIFY(!(std::is_nothrow_constructible, InPlaceInitT, int, int&&>::value)); + CORRADE_VERIFY((std::is_constructible, InPlaceInitT, int>::value)); + CORRADE_VERIFY(!(std::is_nothrow_constructible, InPlaceInitT, int>::value)); +} + +void PointerTest::constructInPlaceMake() { + { + /* Using int{} to test perfect forwarding */ + auto a = pointer(1337, int{}); + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 1337); + } + + CORRADE_COMPARE(Immovable::constructed, 1); + CORRADE_COMPARE(Immovable::destructed, 1); +} + +void PointerTest::constructInPlaceMakeAmbiguous() { + struct Ambiguous { + Ambiguous() = default; + Ambiguous(Ambiguous* parent, int = {}): parent{parent} {} + Ambiguous(const Ambiguous&) = delete; + Ambiguous& operator=(const Ambiguous&) = delete; + Ambiguous* parent{}; + }; + + /* All the commented-out calls below should static_assert because they're + ambiguous */ + Ambiguous parent; + //auto a = pointer(&parent); + //auto b = pointer(new Ambiguous); + //auto c = pointer(&parent); + //auto d = pointer(new Ambiguous); + auto e = pointer(); + auto f = pointer(&parent, 32); + auto g = Pointer{InPlaceInit, &parent}; + auto h = Pointer{new Ambiguous}; + //CORRADE_COMPARE(a->parent, &parent); + //CORRADE_COMPARE(b->parent, nullptr); + //CORRADE_COMPARE(c->parent, &parent); + //CORRADE_COMPARE(d->parent, nullptr); + CORRADE_COMPARE(e->parent, nullptr); + CORRADE_COMPARE(f->parent, &parent); + CORRADE_COMPARE(g->parent, &parent); + CORRADE_COMPARE(h->parent, nullptr); +} + +void PointerTest::boolConversion() { + Pointer a; + Pointer b{new int{5}}; + + CORRADE_VERIFY(!a); + CORRADE_VERIFY(b); + CORRADE_VERIFY(!!b); + + CORRADE_VERIFY(!(std::is_convertible, int>::value)); + CORRADE_VERIFY(!(std::is_convertible, bool>::value)); +} + +void PointerTest::compareToNullptr() { + Pointer a; + Pointer b{new int{5}}; + + CORRADE_VERIFY(a == nullptr); + CORRADE_VERIFY(b != nullptr); +} + +void PointerTest::access() { + { + Pointer a{InPlaceInit, 5}; + const Pointer ca{InPlaceInit, 8}; + + CORRADE_COMPARE(a->a, 5); + CORRADE_COMPARE(ca->a, 8); + CORRADE_COMPARE((*a).a, 5); + CORRADE_COMPARE((*ca).a, 8); + CORRADE_COMPARE(a.get()->a, 5); + CORRADE_COMPARE(ca.get()->a, 8); + } + + CORRADE_COMPARE(Immovable::constructed, 2); + CORRADE_COMPARE(Immovable::destructed, 2); +} + +void PointerTest::accessInvalid() { + struct Innocent { + void foo() const {} + }; + + Pointer a; + const Pointer ca; + + CORRADE_VERIFY(!a); + CORRADE_VERIFY(!ca); + + std::ostringstream out; + { + Error redirectError{&out}; + a->foo(); + ca->foo(); + (*a).foo(); + (*ca).foo(); + } + CORRADE_COMPARE(out.str(), + "Containers::Pointer: the pointer is null\n" + "Containers::Pointer: the pointer is null\n" + "Containers::Pointer: the pointer is null\n" + "Containers::Pointer: the pointer is null\n"); +} + +void PointerTest::reset() { + { + Pointer a{InPlaceInit, 5}; + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 5); + + a.reset(new Immovable{16}); + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 16); + } + + CORRADE_COMPARE(Immovable::constructed, 2); + CORRADE_COMPARE(Immovable::destructed, 2); +} + +void PointerTest::emplace() { + { + Pointer a{InPlaceInit, 5}; + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 5); + + /* Using int{} to test perfect forwarding */ + a.emplace(16, int{}); + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 16); + } + + CORRADE_COMPARE(Immovable::constructed, 2); + CORRADE_COMPARE(Immovable::destructed, 2); +} + +void PointerTest::release() { + Immovable* ptr; + { + Pointer a{InPlaceInit, 5}; + CORRADE_VERIFY(a); + CORRADE_COMPARE(a->a, 5); + + ptr = a.release(); + CORRADE_VERIFY(!a); + CORRADE_VERIFY(ptr); + } + + delete ptr; + + CORRADE_COMPARE(Immovable::constructed, 1); + CORRADE_COMPARE(Immovable::destructed, 1); +} + +void PointerTest::debug() { + std::stringstream out; + std::intptr_t a = 0xdeadbeef; + Pointer aptr{reinterpret_cast(a)}; + Debug{&out} << aptr << Pointer{} << nullptr; + aptr.release(); + CORRADE_COMPARE(out.str(), "0xdeadbeef 0x0 nullptr\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Corrade::Containers::Test::PointerTest)