diff --git a/doc/corrade-changelog.dox b/doc/corrade-changelog.dox index 967bc354d..1f7eb89f8 100644 --- a/doc/corrade-changelog.dox +++ b/doc/corrade-changelog.dox @@ -60,6 +60,8 @@ namespace Corrade { initialization - Added r-value overloads to @ref Containers::Optional::operator*() allowing you to easily take the value out of a r-value optional +- Opt-in STL compatibility for @ref Containers::Optional, allowing for + *explicit* conversion from and to @cpp std::optional @ce in C++17. - New @ref Containers::arrayCast(StridedArrayView) overload for casting strided array views diff --git a/doc/snippets/CMakeLists.txt b/doc/snippets/CMakeLists.txt index e98e177b7..52a9266c7 100644 --- a/doc/snippets/CMakeLists.txt +++ b/doc/snippets/CMakeLists.txt @@ -39,6 +39,20 @@ add_library(snippets STATIC target_link_libraries(snippets PRIVATE CorradeUtility) set_target_properties(snippets PROPERTIES FOLDER "Corrade/doc/snippets") +# Copied verbatim from src/Corrade/Test/CMakeLists.txt, please keep in sync +# (especially for AppleClang) +if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "7.0") OR + (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0") OR + #(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10.0") OR + (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.10")) + add_library(snippets-cpp17 STATIC + Containers-stl17.cpp) + target_link_libraries(snippets-cpp17 PRIVATE CorradeUtility) + set_target_properties(snippets-cpp17 PROPERTIES + CORRADE_CXX_STANDARD 17 + FOLDER "Corrade/doc/snippets") +endif() + if(WITH_INTERCONNECT) add_library(snippets-Interconnect STATIC Interconnect.cpp) target_link_libraries(snippets-Interconnect PRIVATE CorradeInterconnect) diff --git a/doc/snippets/Containers-stl17.cpp b/doc/snippets/Containers-stl17.cpp new file mode 100644 index 000000000..6c0a6274e --- /dev/null +++ b/doc/snippets/Containers-stl17.cpp @@ -0,0 +1,39 @@ +/* + 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 "Corrade/Containers/OptionalStl.h" + +using namespace Corrade; + +int main() { +{ +/* [Optional] */ +std::optional a{5}; +Containers::Optional b(a); + +std::optional c(Containers::optional("hello")); +/* [Optional] */ +} +} diff --git a/src/Corrade/Containers/CMakeLists.txt b/src/Corrade/Containers/CMakeLists.txt index c95abe825..5ac683d80 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 + OptionalStl.h Pointer.h PointerStl.h Reference.h diff --git a/src/Corrade/Containers/Optional.h b/src/Corrade/Containers/Optional.h index 94a0c9b73..fbdffcd0a 100644 --- a/src/Corrade/Containers/Optional.h +++ b/src/Corrade/Containers/Optional.h @@ -27,6 +27,7 @@ /** @file * @brief Class @ref Corrade::Containers::Optional, tag type @ref Corrade::Containers::NullOptT, tag @ref Corrade::Containers::NullOpt, function @ref Corrade::Containers::optional() + * @see @ref Corrade/Containers/OptionalStl.h */ #include @@ -41,6 +42,10 @@ namespace Corrade { namespace Containers { +namespace Implementation { + template struct OptionalConverter; +} + /** @brief Null optional initialization tag type @@ -80,6 +85,18 @@ Unlike `std::optional`, this class does not provide a @cpp constexpr @ce implementation or ordering operators, which makes it fairly simple and lightweight. If you need the extra features, use the standard `std::optional`. +@section Containers-Optional-stl STL compatibility + +Instances of @ref Optional are *explicitly* copy- and move-convertible to and +from @cpp std::optional @ce if you include @ref Corrade/Containers/OptionalStl.h +and build your code with C++17 enabled. The conversion is provided in a +separate header to avoid unconditional @cpp #include @ce, which +significantly affects compile times. Example: + +@snippet Containers-stl17.cpp Optional + + + @m_class{m-block m-success} @par Single-header version @@ -132,6 +149,12 @@ template class Optional { new(&_value.v) T{std::forward(args)...}; } + /** @brief Copy-construct an optional from external representation */ + template::from(std::declval()))> explicit Optional(const U& other) noexcept(std::is_nothrow_copy_constructible::value): Optional{Implementation::OptionalConverter::from(other)} {} + + /** @brief Move-construct an optional from external representation */ + template::from(std::declval()))> explicit Optional(U&& other) noexcept(std::is_nothrow_move_constructible::value): Optional{Implementation::OptionalConverter::from(std::move(other))} {} + /** @brief Copy constructor */ Optional(const Optional& other) noexcept(std::is_nothrow_copy_constructible::value); @@ -156,6 +179,16 @@ template class Optional { */ Optional& operator=(Optional&& other) noexcept(std::is_nothrow_move_assignable::value); + /** @brief Copy-convert the optional to external representation */ + template::to(std::declval&>()))> explicit operator U() const & { + return Implementation::OptionalConverter::to(*this); + } + + /** @brief Move-convert the optional to external representation */ + template::to(std::declval&&>()))> explicit operator U() && { + return Implementation::OptionalConverter::to(std::move(*this)); + } + /** * @brief Clear the contained value * diff --git a/src/Corrade/Containers/OptionalStl.h b/src/Corrade/Containers/OptionalStl.h new file mode 100644 index 000000000..246a39fb8 --- /dev/null +++ b/src/Corrade/Containers/OptionalStl.h @@ -0,0 +1,66 @@ +#ifndef Corrade_Containers_PointerStl_h +#define Corrade_Containers_PointerStl_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 STL compatibility for @ref Corrade::Containers::Optional + +Including this header allows you to *explicitly* convert between +@ref Corrade::Containers::Optional and @cpp std::optional @ce using copy / move +construction and assignment. See @ref Containers-Optional-stl for more +information. +*/ + +#include + +#include "Corrade/Containers/Optional.h" + +/* Listing these namespaces doesn't anything to the docs, so don't */ +#ifndef DOXYGEN_GENERATING_OUTPUT +namespace Corrade { namespace Containers { namespace Implementation { + +template struct OptionalConverter> { + static Optional from(const std::optional& other) { + return other ? Optional{*other} : Containers::NullOpt; + } + + static Optional from(std::optional&& other) { + return other ? Optional{*std::move(other)} : Containers::NullOpt; + } + + static std::optional to(const Optional& other) { + return other ? std::optional{*other} : std::nullopt; + } + + static std::optional to(Optional&& other) { + return other ? std::optional{*std::move(other)} : std::nullopt; + } +}; + +}}} +#endif + +#endif diff --git a/src/Corrade/Containers/Test/CMakeLists.txt b/src/Corrade/Containers/Test/CMakeLists.txt index aef4abeb3..f1a1ea576 100644 --- a/src/Corrade/Containers/Test/CMakeLists.txt +++ b/src/Corrade/Containers/Test/CMakeLists.txt @@ -63,3 +63,15 @@ set_target_properties( ContainersStridedArrayViewTest ContainersTagsTest PROPERTIES FOLDER "Corrade/Containers/Test") + +# Copied verbatim from src/Corrade/Test/CMakeLists.txt, please keep in sync +# (especially for AppleClang) +if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "7.0") OR + (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0") OR + #(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10.0") OR + (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.10")) + corrade_add_test(ContainersOptionalStlTest OptionalStlTest.cpp) + set_target_properties(ContainersOptionalStlTest PROPERTIES + CORRADE_CXX_STANDARD 17 + FOLDER "Corrade/Containers/Test") +endif() diff --git a/src/Corrade/Containers/Test/OptionalStlTest.cpp b/src/Corrade/Containers/Test/OptionalStlTest.cpp new file mode 100644 index 000000000..b2b25fc62 --- /dev/null +++ b/src/Corrade/Containers/Test/OptionalStlTest.cpp @@ -0,0 +1,103 @@ + +/* + 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 "Corrade/Containers/OptionalStl.h" +#include "Corrade/Containers/Pointer.h" +#include "Corrade/TestSuite/Tester.h" + +namespace Corrade { namespace Containers { namespace Test { namespace { + +struct OptionalStlTest: TestSuite::Tester { + explicit OptionalStlTest(); + + void convertCopy(); + void convertCopyNull(); + void convertMove(); + void convertMoveNull(); +}; + +OptionalStlTest::OptionalStlTest() { + addTests({&OptionalStlTest::convertCopy, + &OptionalStlTest::convertCopyNull, + &OptionalStlTest::convertMove, + &OptionalStlTest::convertMoveNull}); +} + +void OptionalStlTest::convertCopy() { + std::optional a = 5; + CORRADE_VERIFY(a); + CORRADE_COMPARE(*a, 5); + + Optional b{a}; + CORRADE_VERIFY(b); + CORRADE_COMPARE(*b, 5); + + std::optional c{b}; + CORRADE_VERIFY(c); + CORRADE_COMPARE(*c, 5); +} + +void OptionalStlTest::convertCopyNull() { + std::optional a; + CORRADE_VERIFY(!a); + + Optional b(a); + CORRADE_VERIFY(!b); + + std::optional c(b); + CORRADE_VERIFY(!c); +} + +void OptionalStlTest::convertMove() { + std::optional> a{pointer(new int{15})}; + CORRADE_VERIFY(a); + CORRADE_COMPARE(**a, 15); + + Optional> b(std::move(a)); + CORRADE_VERIFY(b); + CORRADE_VERIFY(!*a); + CORRADE_COMPARE(**b, 15); + + std::optional> c(std::move(b)); + CORRADE_VERIFY(c); + CORRADE_VERIFY(!*b); + CORRADE_COMPARE(**c, 15); +} + +void OptionalStlTest::convertMoveNull() { + std::optional> a; + CORRADE_VERIFY(!a); + + Optional> b(std::move(a)); + CORRADE_VERIFY(!b); + + std::optional> c(std::move(b)); + CORRADE_VERIFY(!c); +} + +}}}} + +CORRADE_TEST_MAIN(Corrade::Containers::Test::OptionalStlTest) diff --git a/src/Corrade/Containers/Test/OptionalTest.cpp b/src/Corrade/Containers/Test/OptionalTest.cpp index 9abf4c098..ad7585a1a 100644 --- a/src/Corrade/Containers/Test/OptionalTest.cpp +++ b/src/Corrade/Containers/Test/OptionalTest.cpp @@ -29,7 +29,63 @@ #include "Corrade/Containers/Optional.h" #include "Corrade/TestSuite/Tester.h" -namespace Corrade { namespace Containers { namespace Test { namespace { +namespace { + +struct MaybeInt { + MaybeInt(int a): a{a} {} + + int a; +}; + +struct MaybePtr { + MaybePtr(int* a): a{a} {} + MaybePtr(const MaybePtr&) = delete; + MaybePtr(MaybePtr&& other): a{other.a} { + other.a = nullptr; + } + ~MaybePtr() { delete a; } + MaybePtr& operator=(const MaybePtr&) = delete; + MaybePtr& operator=(MaybePtr&& other) { + std::swap(a, other.a); + return *this; + } + + int* a; +}; + +} + +namespace Corrade { namespace Containers { + +namespace Implementation { + +template<> struct OptionalConverter { + static Optional from(const MaybeInt& other) { + return other.a; + } + + static MaybeInt to(const Optional& other) { + return *other; + } +}; + +template<> struct OptionalConverter { + static Optional from(MaybePtr&& other) { + Optional ret{other.a}; + other.a = nullptr; + return ret; + } + + static MaybePtr to(Optional&& other) { + MaybePtr ret{*other}; + other = NullOpt; + return ret; + } +}; + +} + +namespace Test { namespace { struct OptionalTest: TestSuite::Tester { explicit OptionalTest(); @@ -46,6 +102,8 @@ struct OptionalTest: TestSuite::Tester { void constructInPlace(); void constructInPlaceMake(); void constructInPlaceMakeAmbiguous(); + void convertCopy(); + void convertMove(); void constructCopyFromNull(); void constructCopyFromSet(); @@ -98,9 +156,12 @@ OptionalTest::OptionalTest() { &OptionalTest::constructMoveMake, &OptionalTest::constructInPlace, &OptionalTest::constructInPlaceMake, - &OptionalTest::constructInPlaceMakeAmbiguous, + &OptionalTest::constructInPlaceMakeAmbiguous}, &OptionalTest::resetCounters, &OptionalTest::resetCounters); + + addTests({&OptionalTest::convertCopy, + &OptionalTest::convertMove}); - &OptionalTest::constructCopyFromNull, + addTests({&OptionalTest::constructCopyFromNull, &OptionalTest::constructCopyFromSet, &OptionalTest::constructMoveFromNull, @@ -428,6 +489,43 @@ void OptionalTest::constructInPlaceMakeAmbiguous() { CORRADE_COMPARE(h->parent, nullptr); } +void OptionalTest::convertCopy() { + MaybeInt a(5); + CORRADE_COMPARE(a.a, 5); + + Optional b(a); + CORRADE_COMPARE(*b, 5); + + MaybeInt c(b); + CORRADE_COMPARE(c.a, 5); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible>::value)); + CORRADE_VERIFY(!(std::is_convertible&, MaybeInt>::value)); +} + +void OptionalTest::convertMove() { + MaybePtr a(new int{35}); + CORRADE_COMPARE(*a.a, 35); + + Optional b(std::move(a)); + CORRADE_COMPARE(**b, 35); + CORRADE_VERIFY(!a.a); + + MaybePtr c(std::move(b)); + CORRADE_COMPARE(*c.a, 35); + CORRADE_VERIFY(!b); + + /* Copy construction is not allowed */ + CORRADE_VERIFY((std::is_constructible, MaybePtr&&>::value)); + CORRADE_VERIFY(!(std::is_constructible, const MaybePtr&>::value)); + /** @todo how to check for explicit convertibility?? */ + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible>::value)); + CORRADE_VERIFY(!(std::is_convertible&&, MaybePtr>::value)); +} + void OptionalTest::constructCopyFromNull() { { Optional a; diff --git a/src/Corrade/Test/CMakeLists.txt b/src/Corrade/Test/CMakeLists.txt index e3d9e8999..e5f0d3d13 100644 --- a/src/Corrade/Test/CMakeLists.txt +++ b/src/Corrade/Test/CMakeLists.txt @@ -52,6 +52,8 @@ endif() # - Clang reports proper __cplusplus since 5.0 (tested manually, no idea about # AppleClang) # - MSVC since 2017 +# This same expression is used in doc/snippets/CMakeLists.txt and +# src/Corrade/Containers/Test/CMakeLists.txt, please keep in sync. if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "7.0") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0") OR #(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10.0") OR