// (C) 2002, Fernando Luis Cacciola Carballal. // // This material is provided "as is", with absolutely no warranty expressed // or implied. Any use is at your own risk. // // Permission to use or copy this software for any purpose is hereby granted // without fee, provided the above notices are retained on all copies. // Permission to modify the code and to distribute modified code is granted, // provided the above notices are retained, and a notice that the code was // modified is included with the above copyright notice. // // You are welcome to contact the author at: // fernando_cacciola@hotmail.com // #include #include #include #define BOOST_DEBUG 1 #include "boost/optional.hpp" #ifdef __BORLANDC__ #pragma hdrstop #endif #include "boost/test/minimal.hpp" bool boost_error(char const * expr, char const * func, char const * file, long line) { throw std::logic_error( std::string("Boost Error: assertion failed at\nfile: ") + std::string(file) + std::string("\nfunction: ") + std::string(func) ) ; } using boost::optional ; #ifdef BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP using boost::swap ; using boost::get_pointer ; #endif #define TRACE_LIFETIME(msg) if ( trace_lifetime ) { std::cout << msg << std::endl ; } #define ARG(T) (static_cast< T const* >(0)) //#define SHOW_COMPILATION_FAIL_1 //#define SHOW_COMPILATION_FAIL_2 //#define SHOW_COMPILATION_FAIL_3 //#define SHOW_COMPILATION_FAIL_4a //#define SHOW_COMPILATION_FAIL_4b //#define SHOW_COMPILATION_FAIL_5a //#define SHOW_COMPILATION_FAIL_5b // // Helper class used to verify the lifetime managment of the values held by optional // class X { public : X ( int av ) : v(av) { ++ count ; TRACE_LIFETIME ( "X::X(" << av << "). this=" << this ) ; } X ( X const& rhs ) : v(rhs.v) { pending_copy = false ; TRACE_LIFETIME ( "X::X( X const& rhs). this=" << this << " rhs.v=" << rhs.v ) ; if ( throw_on_copy ) { TRACE_LIFETIME ( "throwing exception in X's copy ctor" ) ; throw 0 ; } ++ count ; } ~X() { pending_dtor = false ; -- count ; TRACE_LIFETIME ( "X::~X(). v=" << v << " this=" << this ); } X& operator= ( X const& rhs ) { v = rhs.v ; TRACE_LIFETIME ( "X::operator =( X const& rhs). this=" << this << " rhs.v=" << rhs.v ) ; return *this ; } friend bool operator == ( X const& a, X const& b ) { return a.v == b.v ; } friend bool operator != ( X const& a, X const& b ) { return a.v != b.v ; } friend bool operator < ( X const& a, X const& b ) { return a.v < b.v ; } int V() const { return v ; } int& V() { return v ; } static int count ; static bool pending_copy ; static bool pending_dtor ; static bool trace_lifetime ; static bool throw_on_copy ; private : int v ; private : X() ; } ; int X::count = 0 ; bool X::trace_lifetime = false ; bool X::pending_copy = false ; bool X::pending_dtor = false ; bool X::throw_on_copy = false ; inline void set_pending_copy ( X const* x ) { X::pending_copy = true ; } inline void set_pending_dtor ( X const* x ) { X::pending_dtor = true ; } inline void set_throw_on_copy ( X const* x ) { X::throw_on_copy = true ; } inline void reset_throw_on_copy ( X const* x ) { X::throw_on_copy = false ; } inline void check_is_pending_copy ( X const* x ) { BOOST_CHECK( X::pending_copy ) ; } inline void check_is_pending_dtor ( X const* x ) { BOOST_CHECK( X::pending_dtor ) ; } inline void check_is_not_pending_copy( X const* x ) { BOOST_CHECK( !X::pending_copy ) ; } inline void check_is_not_pending_dtor( X const* x ) { BOOST_CHECK( !X::pending_dtor ) ; } inline void check_instance_count ( int c, X const* x ) { BOOST_CHECK( X::count == c ) ; } inline int get_instance_count ( X const* x ) { return X::count ; } inline void set_pending_copy (...) {} inline void set_pending_dtor (...) {} inline void set_throw_on_copy (...) {} inline void reset_throw_on_copy (...) {} inline void check_is_pending_copy (...) {} inline void check_is_pending_dtor (...) {} inline void check_is_not_pending_copy(...) {} inline void check_is_not_pending_dtor(...) {} inline void check_instance_count (...) {} inline int get_instance_count (...) { return 0 ; } template inline void check_uninitialized_const ( optional const& opt ) { BOOST_CHECK( opt == 0 ) ; BOOST_CHECK( !opt ) ; BOOST_CHECK( !get_pointer(opt) ) ; BOOST_CHECK( !opt.get() ) ; } template inline void check_uninitialized ( optional& opt ) { BOOST_CHECK( opt == 0 ) ; BOOST_CHECK( !opt ) ; BOOST_CHECK( !get_pointer(opt) ) ; BOOST_CHECK( !opt.get() ) ; check_uninitialized_const(opt); } template inline void check_initialized_const ( optional const& opt ) { BOOST_CHECK( opt ) ; BOOST_CHECK( opt != 0 ) ; BOOST_CHECK ( !!opt ) ; BOOST_CHECK ( get_pointer(opt) ) ; BOOST_CHECK ( opt.get() ) ; } template inline void check_initialized ( optional& opt ) { BOOST_CHECK( opt ) ; BOOST_CHECK( opt != 0 ) ; BOOST_CHECK ( !!opt ) ; BOOST_CHECK ( get_pointer(opt) ) ; BOOST_CHECK ( opt.get() ) ; check_initialized_const(opt); } template inline void check_value_const ( optional const& opt, T const& v, T const& z ) { BOOST_CHECK( *opt == v ) ; BOOST_CHECK( *opt != z ) ; BOOST_CHECK( (*(opt.operator->()) == v) ) ; BOOST_CHECK( *get_pointer(opt) == v ) ; BOOST_CHECK( *opt.get() == v ) ; } template inline void check_value ( optional& opt, T const& v, T const& z ) { #ifdef _MSC_VER // For some reason, VC6.0 is creating a temporary while evaluating (*opt == v), // so we need to turn throw on copy off first. reset_throw_on_copy( ARG(T) ) ; #endif BOOST_CHECK( *opt == v ) ; BOOST_CHECK( *opt != z ) ; BOOST_CHECK( (*(opt.operator->()) == v) ) ; BOOST_CHECK( *get_pointer(opt) == v ) ; BOOST_CHECK( *opt.get() == v ) ; check_value_const(opt,v,z); } // // Basic test. // Check ordinary functionality: // Initialization, assignment, comparison and value-accessing. // template void test_basics( T const* ) { std::cout << std::endl ; T z(-1); T a(1); // Default construction. // 'def' state is Uninitialized. // T::T() is not called (and it is not even defined) optional def ; check_uninitialized(def); // Direct initialization. // 'oa' state is Initialized with 'a' // T::T( T const& x ) is used. set_pending_copy( ARG(T) ) ; optional oa ( a ) ; check_is_not_pending_copy( ARG(T) ); check_initialized(oa); check_value(oa,a,z); T b(2); optional ob ; // Value-Assignment upon Uninitialized optional. // T::T ( T const& x ) is used. set_pending_copy( ARG(T) ) ; ob.reset(a) ; check_is_not_pending_copy( ARG(T) ) ; check_initialized(ob); check_value(ob,a,z); // Value-Assignment upon Initialized optional. // This uses T::operator= ( T const& x ) directly // on the reference returned by operator*() set_pending_dtor( ARG(T) ) ; set_pending_copy( ARG(T) ) ; *ob = b ; check_is_pending_dtor( ARG(T) ) ; check_is_pending_copy( ARG(T) ) ; check_initialized(ob); check_value(ob,b,z); // Assignment initialization. // T::T ( T const& x ) is used to copy new value. set_pending_copy( ARG(T) ) ; optional const oa2 = oa ; check_is_not_pending_copy( ARG(T) ) ; check_initialized_const(oa2); check_value_const(oa2,a,z); // Assignment // T::~T() is used to destroy previous value in ob. // T::T ( T const& x ) is used to copy new value. set_pending_dtor( ARG(T) ) ; set_pending_copy( ARG(T) ) ; oa = ob ; check_is_not_pending_dtor( ARG(T) ) ; check_is_not_pending_copy( ARG(T) ) ; check_initialized(oa); check_value(oa,b,z); // Uninitializing Assignment upon Initialized Optional // T::~T() is used to destroy previous value in oa. set_pending_dtor( ARG(T) ) ; set_pending_copy( ARG(T) ) ; oa = def ; check_is_not_pending_dtor( ARG(T) ) ; check_is_pending_copy ( ARG(T) ) ; check_uninitialized(oa); // Uninitializing Assignment upon Uninitialized Optional // (Dtor is not called this time) set_pending_dtor( ARG(T) ) ; set_pending_copy( ARG(T) ) ; oa = def ; check_is_pending_dtor( ARG(T) ) ; check_is_pending_copy( ARG(T) ) ; check_uninitialized(oa); // Deinitialization of Initialized Optional // T::~T() is used to destroy previous value in ob. set_pending_dtor( ARG(T) ) ; ob.reset(); check_is_not_pending_dtor( ARG(T) ) ; check_uninitialized(ob); // Deinitialization of Uninitialized Optional // (Dtor is not called this time) set_pending_dtor( ARG(T) ) ; ob.reset(); check_is_pending_dtor( ARG(T) ) ; check_uninitialized(ob); #ifdef SHOW_COMPILATION_FAIL_1 // This is illegal since 'oa2' is const. *oa2 = oa ; #endif #ifdef SHOW_COMPILATION_FAIL_2 T c(3); // Direct Value Assignment is not allowed. // Use operator*() instead. oa = c ; #endif #ifdef SHOW_COMPILATION_FAIL_3 T d(4); // Direct Value Construction is explicit. optional oc = d ; #endif } // // Test Direct Value Manipulation // template void test_direct_value_manip( T const* ) { T x(1); optional const c_opt0(x) ; optional opt0(x); BOOST_CHECK( c_opt0->V() == x.V() ) ; BOOST_CHECK( opt0->V() == x.V() ) ; BOOST_CHECK( (*c_opt0).V() == x.V() ) ; BOOST_CHECK( (* opt0).V() == x.V() ) ; T y(2); *opt0 = y ; BOOST_CHECK( (*opt0).V() == y.V() ) ; BOOST_CHECK( x < (*opt0) ) ; } // // Test Uninitialized access assert // template void test_uninitialized_access( T const* ) { optional def ; bool passed = false ; try { // This should throw becasue 'def' is uninitialized T const& n = *def ; (n); passed = true ; } catch (...) {} BOOST_CHECK(!passed); passed = false ; try { T v(1) ; (v); // This should throw becasue 'def' is uninitialized *def = v ; passed = true ; } catch (...) {} BOOST_CHECK(!passed); passed = false ; try { // This should throw becasue 'def' is uninitialized T v = *(def.operator->()) ; (v); passed = true ; } catch (...) {} BOOST_CHECK(!passed); } // // Test Direct Initialization of optional for a T with throwing copy-ctor. // template void test_throwing_direct_init( T const* ) { T a(1234); int count = get_instance_count( ARG(T) ) ; set_throw_on_copy( ARG(T) ) ; bool passed = false ; try { // This should: // Attempt to copy construct 'a' and throw. // 'opt' won't be constructed. set_pending_copy( ARG(T) ) ; optional opt(a) ; passed = true ; } catch ( ... ){} BOOST_CHECK(!passed); check_is_not_pending_copy( ARG(T) ); check_instance_count(count, ARG(T) ); } // // Test Value Assignment to an Uninitialized optional for a T with a throwing copy-ctor // template void test_throwing_val_assign_on_uninitialized( T const* ) { T a(1234); int count = get_instance_count( ARG(T) ) ; set_throw_on_copy( ARG(T) ) ; optional opt ; bool passed = false ; try { // This should: // Attempt to copy construct 'a' and throw. // opt should be left uninitialized. set_pending_copy( ARG(T) ) ; opt.reset( a ); passed = true ; } catch ( ... ) {} BOOST_CHECK(!passed); check_is_not_pending_copy( ARG(T) ); check_instance_count(count, ARG(T) ); check_uninitialized(opt); } // // Test Value Reset on an Initialized optional for a T with a throwing copy-ctor // template void test_throwing_val_assign_on_initialized( T const* ) { T z(-1); T a(1234); T b(5678); int count = get_instance_count( ARG(T) ) ; reset_throw_on_copy( ARG(T) ) ; optional opt ( b ) ; ++ count ; check_instance_count(count, ARG(T) ); check_value(opt,b,z); set_throw_on_copy( ARG(T) ) ; bool passed = false ; try { // This should: // Attempt to copy construct 'a' and throw. // opt should be left uninitialized (even though it was initialized) set_pending_dtor( ARG(T) ) ; set_pending_copy( ARG(T) ) ; opt.reset ( a ) ; passed = true ; } catch ( ... ) {} BOOST_CHECK(!passed); -- count ; check_is_not_pending_dtor( ARG(T) ); check_is_not_pending_copy( ARG(T) ); check_instance_count(count, ARG(T) ); check_uninitialized(opt); } // // Test Copy Initialization from an Initialized optional for a T with a throwing copy-ctor // template void test_throwing_copy_initialization( T const* ) { T z(-1); T a(1234); reset_throw_on_copy( ARG(T) ) ; optional opt (a); int count = get_instance_count( ARG(T) ) ; set_throw_on_copy( ARG(T) ) ; bool passed = false ; try { // This should: // Attempt to copy construct 'opt' and throw. // opt1 won't be constructed. set_pending_copy( ARG(T) ) ; optional opt1 = opt ; passed = true ; } catch ( ... ) {} BOOST_CHECK(!passed); check_is_not_pending_copy( ARG(T) ); check_instance_count(count, ARG(T) ); // Nothing should have happened to the source optional. check_initialized(opt); check_value(opt,a,z); } // // Test Assignment to an Uninitialized optional from an Initialized optional // for a T with a throwing copy-ctor // template void test_throwing_assign_to_uninitialized( T const* ) { T z(-1); T a(1234); reset_throw_on_copy( ARG(T) ) ; optional opt0 ; optional opt1(a) ; int count = get_instance_count( ARG(T) ) ; set_throw_on_copy( ARG(T) ) ; bool passed = false ; try { // This should: // Attempt to copy construct 'opt1.value()' into opt0 and throw. // opt0 should be left uninitialized. set_pending_copy( ARG(T) ) ; opt0 = opt1 ; passed = true ; } catch ( ... ) {} BOOST_CHECK(!passed); check_is_not_pending_copy( ARG(T) ); check_instance_count(count, ARG(T) ); check_uninitialized(opt0); } // // Test Assignment to an Initialized optional from an Initialized optional // for a T with a throwing copy-ctor // template void test_throwing_assign_to_initialized( T const* ) { T z(-1); T a(1234); T b(5678); reset_throw_on_copy( ARG(T) ) ; optional opt0(a) ; optional opt1(b) ; int count = get_instance_count( ARG(T) ) ; set_throw_on_copy( ARG(T) ) ; bool passed = false ; try { // This should: // Attempt to copy construct 'opt1.value()' into opt0 and throw. // opt0 should be left uninitialized (even though it was initialized) set_pending_dtor( ARG(T) ) ; set_pending_copy( ARG(T) ) ; opt0 = opt1 ; passed = true ; } catch ( ... ) {} BOOST_CHECK(!passed); -- count ; check_is_not_pending_dtor( ARG(T) ); check_is_not_pending_copy( ARG(T) ); check_instance_count(count, ARG(T) ); check_uninitialized(opt0); } // // Test swap in a no-throwing case // template void test_no_throwing_swap( T const* ) { T z(-1); T a(1234); T b(5678); reset_throw_on_copy( ARG(T) ) ; optional def0 ; optional def1 ; optional opt0(a) ; optional opt1(b) ; int count = get_instance_count( ARG(T) ) ; using boost::swap ; swap(def0,def1); check_uninitialized(def0); check_uninitialized(def1); swap(def0,opt0); check_uninitialized(opt0); check_initialized(def0); check_value(def0,a,z); // restore def0 and opt0 swap(def0,opt0); swap(opt0,opt1); check_instance_count(count, ARG(T) ); check_initialized(opt0); check_initialized(opt1); check_value(opt0,b,z); check_value(opt1,a,z); } // // Test swap in a throwing case // template void test_throwing_swap( T const* ) { T a(1234); T b(5678); reset_throw_on_copy( ARG(T) ) ; optional opt0(a) ; optional opt1(b) ; set_throw_on_copy( ARG(T) ) ; // // Case 1: Both Initialized. // bool passed = false ; try { // This should attempt to swap optionals and fail at swap(X&,X&). swap(opt0,opt1); passed = true ; } catch ( ... ) {} BOOST_CHECK(!passed); // Assuming swap(T&,T&) has at least the basic guarantee, these should hold. BOOST_CHECK( ( !opt0 || ( !!opt0 && ( ( *opt0 == a ) || ( *opt0 == b ) ) ) ) ) ; BOOST_CHECK( ( !opt1 || ( !!opt1 && ( ( *opt1 == a ) || ( *opt1 == b ) ) ) ) ) ; // // Case 2: Only one Initialized. // reset_throw_on_copy( ARG(T) ) ; opt0.reset(); opt1.reset(a); set_throw_on_copy( ARG(T) ) ; passed = false ; try { // This should attempt to swap optionals and fail at opt0.reset(*opt1) // opt0 should be left uninitialized and opt1 unchanged. swap(opt0,opt1); passed = true ; } catch ( ... ) {} BOOST_CHECK(!passed); check_uninitialized(opt0); check_initialized(opt1); check_value(opt1,a,b); } // // This verifies relational operators. // template void test_relops( T const* v ) { reset_throw_on_copy( ARG(T) ) ; T v0(1); T v1(2); T v2(2); optional def0 ; optional def1 ; optional opt0(v0); optional opt1(v1); optional opt2(v2); #ifdef SHOW_COMPILATION_FAIL_4a // You can compare against 0 or against another optional<>, // but not against another value if ( def0 == 1 ) ; #endif #ifdef SHOW_COMPILATION_FAIL_4b if ( def0 != 1 ) ; #endif // Check identity BOOST_CHECK ( def0 == def0 ) ; BOOST_CHECK ( opt0 == opt0 ) ; BOOST_CHECK ( !(def0 != def0) ) ; BOOST_CHECK ( !(opt0 != opt0) ) ; // If both are uininitalized they compare equal BOOST_CHECK ( def0 == def1 ) ; BOOST_CHECK ( !(def0 != def1) ) ; // If only one is initialized they compare unequal BOOST_CHECK ( def0 != opt0 ) ; BOOST_CHECK ( !(def1 == opt1) ) ; // If both are initialized, values are compared BOOST_CHECK ( opt0 != opt1 ) ; BOOST_CHECK ( opt1 == opt2 ) ; } void test_with_builtin_types() { test_basics( ARG(double) ); test_uninitialized_access( ARG(double) ); test_no_throwing_swap( ARG(double) ); test_relops( ARG(double) ) ; } void test_with_class_type() { test_basics( ARG(X) ); test_direct_value_manip( ARG(X) ); test_uninitialized_access( ARG(X) ); test_throwing_direct_init( ARG(X) ); test_throwing_val_assign_on_uninitialized( ARG(X) ); test_throwing_val_assign_on_initialized( ARG(X) ); test_throwing_copy_initialization( ARG(X) ); test_throwing_assign_to_uninitialized( ARG(X) ); test_throwing_assign_to_initialized( ARG(X) ); test_no_throwing_swap( ARG(X) ); test_throwing_swap( ARG(X) ); test_relops( ARG(X) ) ; BOOST_CHECK ( X::count == 0 ) ; } int eat ( char ) { return 1 ; } int eat ( int ) { return 1 ; } int eat ( void const* ) { return 1 ; } template int eat ( T ) { return 0 ; } // // This verifies that operator safe_bool() behaves properly. // template void test_no_implicit_conversions_impl( T const& v ) { optional def ; BOOST_CHECK ( eat(def) == 0 ) ; } void test_no_implicit_conversions() { char c = 0 ; int i = 0 ; void const* p = 0 ; test_no_implicit_conversions_impl(c); test_no_implicit_conversions_impl(i); test_no_implicit_conversions_impl(p); } struct A {} ; void test_conversions() { char c = 123 ; optional opt0(c); optional opt1(opt0); BOOST_CHECK(*opt1 == static_cast(c)); float f = 1.234 ; double d = f ; optional opt2(f) ; optional opt3 ; opt3 = opt2 ; BOOST_CHECK(*opt3 == d); #ifdef SHOW_COMPILATION_FAIL_5a optional opt4(opt0); #endif #ifdef SHOW_COMPILATION_FAIL_5b optional opt5 ; opt5 = opt0; #endif } int test_main( int, char* [] ) { try { test_with_class_type(); test_with_builtin_types(); test_no_implicit_conversions(); test_conversions(); } catch (... ) { BOOST_ERROR("Unexpected Exception caught!"); } return 0; }