//----------------------------------------------------------------------------- // boost-libs variant/test/variant_nonempty_check.cpp source file // See http://www.boost.org for updates, documentation, and revision history. //----------------------------------------------------------------------------- // // Copyright (c) 2014-2019 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 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 inline void check_1_impl(int helper) { boost::variant v; try { v = throwing_class(helper); BOOST_TEST(!v.which()); BOOST_TEST(boost::get(&v)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v.which()); BOOST_TEST(boost::get(&v)); } try { throwing_class tc(helper); v = tc; BOOST_TEST(!v.which()); BOOST_TEST(boost::get(&v)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v.which()); BOOST_TEST(boost::get(&v)); } } inline void check_1(int helper = 1) { check_1_impl(helper); check_1_impl(helper); check_1_impl(helper); check_1_impl(helper); check_1_impl(helper); } template inline void check_2_impl(int helper) { boost::variant v; try { v = throwing_class(helper); BOOST_TEST(v.which() == 1); BOOST_TEST(boost::get(&v)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v.which()); BOOST_TEST(boost::get(&v)); } try { throwing_class cl(helper); v = cl; BOOST_TEST(v.which() == 1); BOOST_TEST(boost::get(&v)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v.which()); BOOST_TEST(boost::get(&v)); } } inline void check_2(int helper = 1) { check_2_impl(helper); check_2_impl(helper); check_2_impl(helper); check_2_impl(helper); check_2_impl(helper); } template inline void check_3_impl(int helper) { boost::variant v1, v2; swap(v1, v2); try { v1 = throwing_class(helper); BOOST_TEST(v1.which() == 1); BOOST_TEST(boost::get(&v1)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v1.which()); BOOST_TEST(boost::get(&v1)); } try { v2 = throwing_class(helper); BOOST_TEST(v2.which() == 1); BOOST_TEST(boost::get(&v2)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v2.which()); BOOST_TEST(boost::get(&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(&v1)); BOOST_TEST(!v2.which()); BOOST_TEST(boost::get(&v2)); v1 = v2; } } inline void check_3(int helper = 1) { check_3_impl(helper); check_3_impl(helper); check_3_impl(helper); check_3_impl(helper); check_3_impl(helper); } inline void check_4(int helper = 1) { // This one has a fallback boost::variant v1, v2; swap(v1, v2); try { v1 = throwing_class(helper); BOOST_TEST(v1.which() == 1); BOOST_TEST(boost::get(&v1)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v1.which()); BOOST_TEST(boost::get(&v1)); } try { v2 = throwing_class(helper); BOOST_TEST(v2.which() == 1); BOOST_TEST(boost::get(&v2)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(!v2.which()); BOOST_TEST(boost::get(&v2)); } if (!v1.which() && !v2.which()) { swap(v1, v2); BOOST_TEST(!v1.which()); BOOST_TEST(boost::get(&v1)); BOOST_TEST(!v2.which()); BOOST_TEST(boost::get(&v2)); v1 = v2; } } template inline void check_5_impl(int helper) { boost::variant 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(v1).trash = 1; boost::get(v2).trash = 1; try { v1 = throwing_class(helper); BOOST_TEST(v1.which() == 1); BOOST_TEST(boost::get(&v1)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(v1.which() == 1); BOOST_TEST(boost::get(&v1)); } boost::get(v1).trash = throwing_class::do_not_throw; boost::get(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(&v1)); } catch (const exception_on_assignment& /*e*/) { BOOST_TEST(v1.which() == 0); BOOST_TEST(boost::get(&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(helper); check_5_impl(helper); check_5_impl(helper); check_5_impl(helper); check_5_impl(helper); } template inline void check_6_impl(int helper) { boost::variant 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(v1).trash = 1; boost::get(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(&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(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(v1).trash == helper); } } inline void check_6(int helper = 1) { check_6_impl(helper); check_6_impl(helper); check_6_impl(helper); check_6_impl(helper); check_6_impl(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(); }