| //----------------------------------------------------------------------------- |
| // boost-libs variant/test/variant_nonempty_check.cpp source file |
| // See http://www.boost.org for updates, documentation, and revision history. |
| //----------------------------------------------------------------------------- |
| // |
| // Copyright (c) 2014-2021 Antony Polukhin |
| // |
| // Distributed under the Boost Software License, Version 1.0. (See |
| // accompanying file LICENSE_1_0.txt or copy at |
| // http://www.boost.org/LICENSE_1_0.txt) |
| |
| |
| // In this file we are making tests to ensure that variant guarantees nonemptiness. |
| // |
| // For that purpose we create a `throwing_class`, that throws exception at a specified |
| // assignment attempt. If exception was thrown during move/assignemnt operation we make sure |
| // that data in variant is same as before move/assignemnt operation or that a fallback type is |
| // stored in variant. |
| // |
| // Different nonthrowing_class'es are used to tests different variant internal policies: |
| // with/without fallback type + throw/nothrow copyable + throw/nothrow movable |
| |
| |
| #include "boost/variant/variant.hpp" |
| #include "boost/variant/get.hpp" |
| #include "boost/core/lightweight_test.hpp" |
| #include <stdexcept> |
| |
| struct exception_on_assignment : std::exception {}; |
| struct exception_on_move_assignment : exception_on_assignment {}; |
| |
| void prevent_compiler_noexcept_detection() { |
| char* p = new char; |
| *p = '\0'; |
| delete p; |
| } |
| |
| |
| struct throwing_class { |
| int trash; |
| enum helper_enum { |
| do_not_throw = 780, |
| throw_after_5, |
| throw_after_4, |
| throw_after_3, |
| throw_after_2, |
| throw_after_1 |
| }; |
| |
| bool is_throw() { |
| if (trash < do_not_throw) { |
| return true; |
| } |
| |
| if (trash > do_not_throw && trash <= throw_after_1) { |
| ++ trash; |
| return false; |
| } |
| |
| return trash != do_not_throw; |
| } |
| |
| throwing_class(int value = 123) BOOST_NOEXCEPT_IF(false) : trash(value) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| throwing_class(const throwing_class& b) BOOST_NOEXCEPT_IF(false) : trash(b.trash) { |
| if (is_throw()) { |
| throw exception_on_assignment(); |
| } |
| } |
| |
| const throwing_class& operator=(const throwing_class& b) BOOST_NOEXCEPT_IF(false) { |
| trash = b.trash; |
| if (is_throw()) { |
| throw exception_on_assignment(); |
| } |
| |
| return *this; |
| } |
| |
| #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES |
| throwing_class(throwing_class&& b) BOOST_NOEXCEPT_IF(false) : trash(b.trash) { |
| if (is_throw()) { |
| throw exception_on_move_assignment(); |
| } |
| } |
| |
| const throwing_class& operator=(throwing_class&& b) BOOST_NOEXCEPT_IF(false) { |
| trash = b.trash; |
| if (is_throw()) { |
| throw exception_on_move_assignment(); |
| } |
| |
| return *this; |
| } |
| #endif |
| |
| virtual ~throwing_class() {} |
| }; |
| |
| struct nonthrowing_class { |
| int trash; |
| |
| nonthrowing_class() BOOST_NOEXCEPT_IF(false) : trash(123) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| nonthrowing_class(const nonthrowing_class&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| const nonthrowing_class& operator=(const nonthrowing_class&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| return *this; |
| } |
| |
| #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES |
| nonthrowing_class(nonthrowing_class&&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| const nonthrowing_class& operator=(nonthrowing_class&&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| return *this; |
| } |
| #endif |
| }; |
| |
| struct nonthrowing_class2 { |
| int trash; |
| |
| nonthrowing_class2() BOOST_NOEXCEPT_IF(false) : trash(123) { |
| prevent_compiler_noexcept_detection(); |
| } |
| }; |
| |
| struct nonthrowing_class3 { |
| int trash; |
| |
| nonthrowing_class3() BOOST_NOEXCEPT_IF(true) : trash(123) {} |
| |
| nonthrowing_class3(const nonthrowing_class3&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| const nonthrowing_class3& operator=(const nonthrowing_class3&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| return *this; |
| } |
| |
| #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES |
| nonthrowing_class3(nonthrowing_class3&&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| const nonthrowing_class3& operator=(nonthrowing_class3&&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| return *this; |
| } |
| #endif |
| }; |
| |
| struct nonthrowing_class4 { |
| int trash; |
| |
| nonthrowing_class4() BOOST_NOEXCEPT_IF(false) : trash(123) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| nonthrowing_class4(const nonthrowing_class4&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| } |
| |
| const nonthrowing_class4& operator=(const nonthrowing_class4&) BOOST_NOEXCEPT_IF(false) { |
| prevent_compiler_noexcept_detection(); |
| return *this; |
| } |
| |
| #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES |
| nonthrowing_class4(nonthrowing_class4&&) BOOST_NOEXCEPT_IF(true) { |
| } |
| |
| const nonthrowing_class4& operator=(nonthrowing_class4&&) BOOST_NOEXCEPT_IF(true) { |
| return *this; |
| } |
| #endif |
| }; |
| |
| |
| // Tests ///////////////////////////////////////////////////////////////////////////////////// |
| |
| |
| template <class Nonthrowing> |
| inline void check_1_impl(int helper) |
| { |
| boost::variant<throwing_class, Nonthrowing> v; |
| try { |
| v = throwing_class(helper); |
| BOOST_TEST(!v.which()); |
| BOOST_TEST(boost::get<throwing_class>(&v)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v.which()); |
| BOOST_TEST(boost::get<throwing_class>(&v)); |
| } |
| |
| try { |
| throwing_class tc(helper); |
| v = tc; |
| BOOST_TEST(!v.which()); |
| BOOST_TEST(boost::get<throwing_class>(&v)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v.which()); |
| BOOST_TEST(boost::get<throwing_class>(&v)); |
| } |
| } |
| |
| inline void check_1(int helper = 1) |
| { |
| check_1_impl<nonthrowing_class>(helper); |
| check_1_impl<nonthrowing_class2>(helper); |
| check_1_impl<nonthrowing_class3>(helper); |
| check_1_impl<nonthrowing_class4>(helper); |
| check_1_impl<boost::blank>(helper); |
| } |
| |
| template <class Nonthrowing> |
| inline void check_2_impl(int helper) |
| { |
| boost::variant<Nonthrowing, throwing_class> v; |
| try { |
| v = throwing_class(helper); |
| BOOST_TEST(v.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v.which()); |
| BOOST_TEST(boost::get<Nonthrowing>(&v)); |
| } |
| |
| try { |
| throwing_class cl(helper); |
| v = cl; |
| BOOST_TEST(v.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v.which()); |
| BOOST_TEST(boost::get<Nonthrowing>(&v)); |
| } |
| } |
| |
| inline void check_2(int helper = 1) |
| { |
| check_2_impl<nonthrowing_class>(helper); |
| check_2_impl<nonthrowing_class2>(helper); |
| check_2_impl<nonthrowing_class3>(helper); |
| check_2_impl<nonthrowing_class4>(helper); |
| check_2_impl<boost::blank>(helper); |
| } |
| |
| template <class Nonthrowing> |
| inline void check_3_impl(int helper) |
| { |
| boost::variant<Nonthrowing, throwing_class> v1, v2; |
| |
| swap(v1, v2); |
| try { |
| v1 = throwing_class(helper); |
| BOOST_TEST(v1.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v1)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v1.which()); |
| BOOST_TEST(boost::get<Nonthrowing>(&v1)); |
| } |
| |
| |
| try { |
| v2 = throwing_class(helper); |
| BOOST_TEST(v2.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v2)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v2.which()); |
| BOOST_TEST(boost::get<Nonthrowing>(&v2)); |
| } |
| |
| |
| if (!v1.which() && !v2.which()) { |
| swap(v1, v2); // Make sure that two backup holders swap well |
| BOOST_TEST(!v1.which()); |
| BOOST_TEST(boost::get<Nonthrowing>(&v1)); |
| BOOST_TEST(!v2.which()); |
| BOOST_TEST(boost::get<Nonthrowing>(&v2)); |
| |
| v1 = v2; |
| } |
| } |
| |
| inline void check_3(int helper = 1) |
| { |
| check_3_impl<nonthrowing_class>(helper); |
| check_3_impl<nonthrowing_class2>(helper); |
| check_3_impl<nonthrowing_class3>(helper); |
| check_3_impl<nonthrowing_class4>(helper); |
| check_3_impl<boost::blank>(helper); |
| } |
| |
| inline void check_4(int helper = 1) |
| { |
| // This one has a fallback |
| boost::variant<int, throwing_class> v1, v2; |
| |
| swap(v1, v2); |
| try { |
| v1 = throwing_class(helper); |
| BOOST_TEST(v1.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v1)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v1.which()); |
| BOOST_TEST(boost::get<int>(&v1)); |
| } |
| |
| |
| try { |
| v2 = throwing_class(helper); |
| BOOST_TEST(v2.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v2)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(!v2.which()); |
| BOOST_TEST(boost::get<int>(&v2)); |
| } |
| |
| if (!v1.which() && !v2.which()) { |
| swap(v1, v2); |
| BOOST_TEST(!v1.which()); |
| BOOST_TEST(boost::get<int>(&v1)); |
| BOOST_TEST(!v2.which()); |
| BOOST_TEST(boost::get<int>(&v2)); |
| |
| v1 = v2; |
| } |
| } |
| |
| template <class Nonthrowing> |
| inline void check_5_impl(int helper) |
| { |
| boost::variant<Nonthrowing, throwing_class> v1, v2; |
| throwing_class throw_not_now; |
| throw_not_now.trash = throwing_class::do_not_throw; |
| v1 = throw_not_now; |
| v2 = throw_not_now; |
| |
| boost::get<throwing_class>(v1).trash = 1; |
| boost::get<throwing_class>(v2).trash = 1; |
| |
| try { |
| v1 = throwing_class(helper); |
| BOOST_TEST(v1.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v1)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(v1.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v1)); |
| } |
| |
| boost::get<throwing_class>(v1).trash = throwing_class::do_not_throw; |
| boost::get<throwing_class>(v2).trash = throwing_class::do_not_throw; |
| v1 = Nonthrowing(); |
| v2 = Nonthrowing(); |
| try { |
| v1 = throwing_class(helper); |
| BOOST_TEST(v1.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v1)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(v1.which() == 0); |
| BOOST_TEST(boost::get<Nonthrowing>(&v1)); |
| } |
| |
| int v1_type = v1.which(); |
| int v2_type = v2.which(); |
| try { |
| swap(v1, v2); // Make sure that backup holders swap well |
| BOOST_TEST(v1.which() == v2_type); |
| BOOST_TEST(v2.which() == v1_type); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(v1.which() == v1_type); |
| BOOST_TEST(v2.which() == v2_type); |
| } |
| } |
| |
| |
| inline void check_5(int helper = 1) |
| { |
| check_5_impl<nonthrowing_class>(helper); |
| check_5_impl<nonthrowing_class2>(helper); |
| check_5_impl<nonthrowing_class3>(helper); |
| check_5_impl<nonthrowing_class4>(helper); |
| check_5_impl<boost::blank>(helper); |
| } |
| |
| template <class Nonthrowing> |
| inline void check_6_impl(int helper) |
| { |
| boost::variant<Nonthrowing, throwing_class> v1, v2; |
| throwing_class throw_not_now; |
| throw_not_now.trash = throwing_class::do_not_throw; |
| v1 = throw_not_now; |
| v2 = throw_not_now; |
| |
| v1 = throw_not_now; |
| v2 = throw_not_now; |
| swap(v1, v2); |
| boost::get<throwing_class>(v1).trash = 1; |
| boost::get<throwing_class>(v2).trash = 1; |
| |
| v1 = throwing_class(throw_not_now); |
| v2 = v1; |
| |
| v1 = Nonthrowing(); |
| try { |
| throwing_class tc; |
| tc.trash = helper; |
| v1 = tc; |
| BOOST_TEST(v1.which() == 1); |
| BOOST_TEST(boost::get<throwing_class>(&v1)); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(v1.which() == 0); |
| } |
| |
| v2 = Nonthrowing(); |
| try { |
| v2 = 2; |
| BOOST_TEST(false); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(v2.which() == 0); |
| } |
| |
| // Probably the most significant test: |
| // unsuccessful swap must preserve old values of variant |
| v1 = throw_not_now; |
| boost::get<throwing_class>(v1).trash = helper; |
| try { |
| swap(v1, v2); |
| } catch (const exception_on_assignment& /*e*/) { |
| BOOST_TEST(v1.which() == 1); |
| BOOST_TEST(v2.which() == 0); |
| BOOST_TEST(boost::get<throwing_class>(v1).trash == helper); |
| } |
| } |
| |
| |
| inline void check_6(int helper = 1) |
| { |
| check_6_impl<nonthrowing_class>(helper); |
| check_6_impl<nonthrowing_class2>(helper); |
| check_6_impl<nonthrowing_class3>(helper); |
| check_6_impl<nonthrowing_class4>(helper); |
| check_6_impl<boost::blank>(helper); |
| } |
| |
| int main() |
| { |
| // throwing_class::throw_after_1 + 1 => throw on first assignment/construction |
| // throwing_class::throw_after_1 => throw on second assignment/construction |
| // throwing_class::throw_after_2 => throw on third assignment/construction |
| // ... |
| for (int i = throwing_class::throw_after_1 + 1; i != throwing_class::do_not_throw; --i) { |
| check_1(i); |
| check_2(i); |
| check_3(i); |
| check_4(i); |
| check_5(i); |
| check_6(i); |
| } |
| |
| return boost::report_errors(); |
| } |