| /* | |
| * Copyright © 2025 Google, Inc. | |
| * | |
| * This is part of HarfBuzz, a text shaping library. | |
| * | |
| * Permission is hereby granted, without written agreement and without | |
| * license or royalty fees, to use, copy, modify, and distribute this | |
| * software and its documentation for any purpose, provided that the | |
| * above copyright notice and the following two paragraphs appear in | |
| * all copies of this software. | |
| * | |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | |
| * DAMAGE. | |
| * | |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
| * | |
| * Google Author(s): Garret Rieger | |
| */ | |
| #include "hb-result.hh" | |
| // Track constructor and destructor counts to verify leak-freedom | |
| static int live_instances = 0; | |
| enum error_code_t { | |
| ERR_A, | |
| ERR_B, | |
| ERR_C, | |
| }; | |
| template <typename T> | |
| using result = hb_result_t<T, error_code_t>; | |
| struct resource_t | |
| { | |
| int id; | |
| explicit resource_t (int id_) : id (id_) { live_instances++; } | |
| resource_t (const resource_t& o) : id (o.id) { live_instances++; } | |
| resource_t (resource_t&& o) : id (o.id) { o.id = -1; live_instances++; } | |
| ~resource_t () { live_instances--; } | |
| resource_t& operator = (const resource_t& o) | |
| { | |
| id = o.id; | |
| return *this; | |
| } | |
| resource_t& operator = (resource_t&& o) | |
| { | |
| id = o.id; | |
| o.id = -1; | |
| return *this; | |
| } | |
| bool operator == (const resource_t& o) const { return id == o.id; } | |
| }; | |
| static result<int> returns_ok (int v) | |
| { | |
| return Ok(v); | |
| } | |
| static result<int> returns_err (error_code_t e) | |
| { | |
| return Err(e); | |
| } | |
| static result<int> try_callee (bool fail, int v, error_code_t e = ERR_A) | |
| { | |
| if (fail) return Err(e); | |
| return Ok(v); | |
| } | |
| static result<void> try_callee_void (bool fail, error_code_t e) | |
| { | |
| if (fail) return e; | |
| return Ok(); | |
| } | |
| static result<int> try_caller (bool fail1, bool fail2) | |
| { | |
| TRY_ASSIGN (int index, try_callee (fail1, 10, ERR_A)); | |
| TRY_ASSIGN (int index2, try_callee (fail2, 20, ERR_B)); | |
| return Ok(index + index2); | |
| } | |
| static result<void> try_caller_void (bool fail) | |
| { | |
| TRY (try_callee_void (fail, ERR_C)); | |
| return Ok(); | |
| } | |
| static result<float> try_caller_type_change (bool fail) | |
| { | |
| TRY_ASSIGN (int index, try_callee (fail, 100, ERR_A)); | |
| return Ok((float) index * 1.5f); | |
| } | |
| static result<int> try_assign_existing (bool fail) | |
| { | |
| int val = 1; | |
| TRY_ASSIGN (val, try_callee (fail, 42, ERR_A)); | |
| return Ok(val); | |
| } | |
| static void test_ok_basic () | |
| { | |
| result<int> r = returns_ok (42); | |
| hb_always_assert (r.is_ok ()); | |
| hb_always_assert (!r.is_err ()); | |
| hb_always_assert ((bool) r); | |
| hb_always_assert (r.value () == 42); | |
| hb_always_assert (*r == 42); | |
| hb_always_assert (r.value_or (0) == 42); | |
| hb_always_assert (r.value_or_default () == 42); | |
| hb_always_assert (r == Ok(42)); | |
| hb_always_assert (Ok(42) == r); | |
| } | |
| static void test_err_basic () | |
| { | |
| result<int> r = returns_err (ERR_A); | |
| hb_always_assert (!r.is_ok ()); | |
| hb_always_assert (r.is_err ()); | |
| hb_always_assert (!r); | |
| hb_always_assert (r.error () == ERR_A); | |
| hb_always_assert (r.value_or (99) == 99); | |
| hb_always_assert (r.value_or_default () == 0); | |
| hb_always_assert (r == Err(ERR_A)); | |
| hb_always_assert (Err(ERR_A) == r); | |
| hb_always_assert (r != Err(ERR_B)); | |
| hb_always_assert (Err(ERR_B) != r); | |
| } | |
| static void test_explicit_ok_err () | |
| { | |
| result<unsigned> r1 = Ok(100u); | |
| hb_always_assert (r1.is_ok ()); | |
| hb_always_assert (r1.value () == 100u); | |
| result<unsigned> r2 = Err(ERR_A); | |
| hb_always_assert (r2.is_err ()); | |
| hb_always_assert (r2.error () == ERR_A); | |
| } | |
| static void test_pointers () | |
| { | |
| int x = 123; | |
| result<int*> r = &x; | |
| hb_always_assert (r.is_ok ()); | |
| hb_always_assert (*r == &x); | |
| hb_always_assert (**r == 123); | |
| **r = 456; | |
| hb_always_assert (x == 456); | |
| } | |
| static void test_void () | |
| { | |
| result<void> r1 = Ok(); | |
| hb_always_assert (r1.is_ok ()); | |
| hb_always_assert (!r1.is_err ()); | |
| hb_always_assert ((bool) r1); | |
| hb_always_assert (r1 == Ok()); | |
| hb_always_assert (r1 != Err(ERR_A)); | |
| hb_always_assert (Ok() == r1); | |
| hb_always_assert (Err(ERR_A) != r1); | |
| result<void> r2 = Err(ERR_A); | |
| hb_always_assert (!r2.is_ok ()); | |
| hb_always_assert (r2.is_err ()); | |
| hb_always_assert (!r2); | |
| hb_always_assert (r2.error () == ERR_A); | |
| hb_always_assert (r2 == Err(ERR_A)); | |
| hb_always_assert (r2 != Err(ERR_B)); | |
| hb_always_assert (r2 != Ok()); | |
| hb_always_assert (Err(ERR_A) == r2); | |
| hb_always_assert (Err(ERR_B) != r2); | |
| hb_always_assert (Ok() != r2); | |
| } | |
| static void test_resource_cleanup () | |
| { | |
| hb_always_assert (live_instances == 0); | |
| { | |
| result<resource_t> r = resource_t (1); | |
| hb_always_assert (live_instances == 1); | |
| hb_always_assert (r.value ().id == 1); | |
| } | |
| hb_always_assert (live_instances == 0); | |
| { | |
| result<resource_t> r1 = resource_t (2); | |
| hb_always_assert (live_instances == 1); | |
| result<resource_t> r2 = r1; | |
| hb_always_assert (live_instances == 2); | |
| result<resource_t> r3 = std::move (r1); | |
| hb_always_assert (live_instances == 3); | |
| } | |
| hb_always_assert (live_instances == 0); | |
| { | |
| result<resource_t> r = resource_t (3); | |
| hb_always_assert (live_instances == 1); | |
| r = Err(ERR_A); | |
| hb_always_assert (live_instances == 0); | |
| hb_always_assert (r.is_err ()); | |
| } | |
| hb_always_assert (live_instances == 0); | |
| } | |
| static void test_try_macro () | |
| { | |
| // Test success path | |
| result<int> r_success = try_caller (false, false); | |
| hb_always_assert (r_success.is_ok ()); | |
| hb_always_assert (r_success.value () == 30); | |
| // Test first failure propagates | |
| result<int> r_fail1 = try_caller (true, false); | |
| hb_always_assert (r_fail1.is_err ()); | |
| hb_always_assert (r_fail1.error () == ERR_A); | |
| // Test second failure propagates | |
| result<int> r_fail2 = try_caller (false, true); | |
| hb_always_assert (r_fail2.is_err ()); | |
| hb_always_assert (r_fail2.error () == ERR_B); | |
| // Test TRY in void function | |
| result<void> v_ok = try_caller_void (false); | |
| hb_always_assert (v_ok.is_ok ()); | |
| result<void> v_err = try_caller_void (true); | |
| hb_always_assert (v_err.is_err ()); | |
| hb_always_assert (v_err.error () == ERR_C); | |
| // Test TRY with different return type | |
| result<float> f_ok = try_caller_type_change (false); | |
| hb_always_assert (f_ok.is_ok ()); | |
| hb_always_assert (f_ok.value () == 150.0f); | |
| result<float> f_err = try_caller_type_change (true); | |
| hb_always_assert (f_err.is_err ()); | |
| hb_always_assert (f_err.error () == ERR_A); | |
| // Test TRY_ASSIGN into existing variable | |
| result<int> a_ok = try_assign_existing (false); | |
| hb_always_assert (a_ok.is_ok ()); | |
| hb_always_assert (a_ok.value () == 42); | |
| result<int> a_err = try_assign_existing (true); | |
| hb_always_assert (a_err.is_err ()); | |
| hb_always_assert (a_err.error () == ERR_A); | |
| } | |
| template <typename T, typename U, typename = void> | |
| struct is_equality_comparable : std::false_type {}; | |
| template <typename T, typename U> | |
| struct is_equality_comparable<T, U, hb_void_t<decltype (hb_declval (T) == hb_declval (U))>> : std::true_type {}; | |
| static void test_same_or_convertible_types () | |
| { | |
| // Comparisons between result and raw types are disallowed | |
| static_assert (!is_equality_comparable<result<int>, int>::value, ""); | |
| static_assert (!is_equality_comparable<result<int>, int64_t>::value, ""); | |
| static_assert (!is_equality_comparable<int, result<int>>::value, ""); | |
| static_assert (!is_equality_comparable<result<int>, error_code_t>::value, ""); | |
| static_assert (!is_equality_comparable<error_code_t, result<int>>::value, ""); | |
| static_assert (!is_equality_comparable<result<void>, error_code_t>::value, ""); | |
| static_assert (is_equality_comparable<result<int>, result<int>>::value, ""); | |
| static_assert (is_equality_comparable<result<int>, hb_result_ok_t<int>>::value, ""); | |
| static_assert (is_equality_comparable<result<int>, hb_result_err_t<error_code_t>>::value, ""); | |
| // Types convertible into each other cannot be directly constructed without Ok/Err | |
| static_assert (!std::is_constructible<hb_result_t<int64_t, int32_t>, int>::value, ""); | |
| static_assert (!std::is_constructible<hb_result_t<int64_t, int32_t>, int64_t>::value, ""); | |
| static_assert (!std::is_constructible<hb_result_t<int64_t, int32_t>, int32_t>::value, ""); | |
| static_assert (std::is_constructible<hb_result_t<int64_t, int32_t>, hb_result_ok_t<int>>::value, ""); | |
| static_assert (std::is_constructible<hb_result_t<int64_t, int32_t>, hb_result_err_t<int>>::value, ""); | |
| // Same types cannot be directly constructed without Ok/Err | |
| static_assert (!std::is_constructible<hb_result_t<int, int>, int>::value, ""); | |
| static_assert (std::is_constructible<hb_result_t<int, int>, hb_result_ok_t<int>>::value, ""); | |
| static_assert (std::is_constructible<hb_result_t<int, int>, hb_result_err_t<int>>::value, ""); | |
| // Types convertible into each other cannot be directly constructed | |
| static_assert (!std::is_constructible<hb_result_t<int, error_code_t>, int>::value, ""); | |
| static_assert (!std::is_constructible<hb_result_t<int, error_code_t>, error_code_t>::value, ""); | |
| // Types not convertible into each other can be directly constructed | |
| static_assert (std::is_constructible<hb_result_t<resource_t, error_code_t>, resource_t>::value, ""); | |
| static_assert (std::is_constructible<hb_result_t<resource_t, error_code_t>, error_code_t>::value, ""); | |
| hb_result_t<int64_t, int32_t> r_ok = Ok(1); | |
| hb_always_assert (r_ok.is_ok ()); | |
| hb_always_assert (r_ok.value () == 1); | |
| hb_result_t<int64_t, int32_t> r_err = Err(2); | |
| hb_always_assert (r_err.is_err ()); | |
| hb_always_assert (r_err.error () == 2); | |
| hb_result_t<int, int> same_ok = Ok(42); | |
| hb_always_assert (same_ok.is_ok ()); | |
| hb_always_assert (same_ok.value () == 42); | |
| hb_result_t<int, int> same_err = Err(99); | |
| hb_always_assert (same_err.is_err ()); | |
| hb_always_assert (same_err.error () == 99); | |
| } | |
| struct any_value_t | |
| { | |
| template <typename X> | |
| any_value_t (X&&) {} | |
| }; | |
| static void test_error_interception() { | |
| // Checks that U&& constructor does not intercept mismatched error tags as a success value. | |
| static_assert (!std::is_constructible<hb_result_t<any_value_t, error_code_t>, hb_result_err_t<const char*>>::value, ""); | |
| hb_result_t<any_value_t, error_code_t> r = Err(ERR_A); | |
| hb_always_assert (r.is_err ()); | |
| hb_result_t<any_value_t, error_code_t> r_ok = Ok(123); | |
| hb_always_assert (r_ok.is_ok ()); | |
| } | |
| struct move_only_err_t | |
| { | |
| int code; | |
| explicit move_only_err_t (int c) : code (c) {} | |
| move_only_err_t (const move_only_err_t&) = delete; | |
| move_only_err_t (move_only_err_t&& o) : code (o.code) { o.code = -1; } | |
| move_only_err_t& operator = (const move_only_err_t&) = delete; | |
| move_only_err_t& operator = (move_only_err_t&& o) { code = o.code; o.code = -1; return *this; } | |
| ~move_only_err_t () = default; | |
| bool operator == (const move_only_err_t& o) const { return code == o.code; } | |
| }; | |
| struct move_only_val_t | |
| { | |
| int val; | |
| explicit move_only_val_t (int v) : val (v) {} | |
| move_only_val_t (const move_only_val_t&) = delete; | |
| move_only_val_t (move_only_val_t&& o) : val (o.val) { o.val = -1; } | |
| move_only_val_t& operator = (const move_only_val_t&) = delete; | |
| move_only_val_t& operator = (move_only_val_t&& o) { val = o.val; o.val = -1; return *this; } | |
| ~move_only_val_t () = default; | |
| bool operator == (const move_only_val_t& o) const { return val == o.val; } | |
| }; | |
| static void test_move_only_types () | |
| { | |
| // Test hb_result_t<T, move_only_err_t> with Err(...) | |
| { | |
| hb_result_t<int, move_only_err_t> r = Err(move_only_err_t (123)); | |
| hb_always_assert (r.is_err ()); | |
| hb_always_assert (r.error ().code == 123); | |
| move_only_err_t err = std::move (r).error (); | |
| hb_always_assert (err.code == 123); | |
| } | |
| // Test direct construction from move_only_err_t | |
| { | |
| hb_result_t<int, move_only_err_t> r = move_only_err_t (456); | |
| hb_always_assert (r.is_err ()); | |
| hb_always_assert (r.error ().code == 456); | |
| } | |
| // Test hb_result_t<void, move_only_err_t> with Err(...) and direct construction | |
| { | |
| hb_result_t<void, move_only_err_t> v1 = Err(move_only_err_t (789)); | |
| hb_always_assert (v1.is_err ()); | |
| hb_always_assert (v1.error ().code == 789); | |
| hb_result_t<void, move_only_err_t> v2 = move_only_err_t (999); | |
| hb_always_assert (v2.is_err ()); | |
| hb_always_assert (v2.error ().code == 999); | |
| } | |
| // Test value_or with move-only value types | |
| { | |
| hb_result_t<move_only_val_t, error_code_t> r_ok = Ok(move_only_val_t (42)); | |
| move_only_val_t v = std::move (r_ok).value_or (move_only_val_t (0)); | |
| hb_always_assert (v.val == 42); | |
| hb_result_t<move_only_val_t, error_code_t> r_err = Err(ERR_A); | |
| move_only_val_t v_fallback = std::move (r_err).value_or (move_only_val_t (99)); | |
| hb_always_assert (v_fallback.val == 99); | |
| } | |
| } | |
| struct copy_move_counter_t | |
| { | |
| static int copy_count; | |
| static int move_count; | |
| static void reset () | |
| { | |
| copy_count = 0; | |
| move_count = 0; | |
| } | |
| int val; | |
| bool in_error() const { | |
| return val == -1; | |
| } | |
| explicit copy_move_counter_t (int v) : val (v) {} | |
| copy_move_counter_t (const copy_move_counter_t& o) : val (o.val) { copy_count++; } | |
| copy_move_counter_t (copy_move_counter_t&& o) : val (o.val) { o.val = -1; move_count++; } | |
| ~copy_move_counter_t () = default; | |
| }; | |
| int copy_move_counter_t::copy_count = 0; | |
| int copy_move_counter_t::move_count = 0; | |
| static result<copy_move_counter_t> return_tracked_from_local () | |
| { | |
| copy_move_counter_t out (42); | |
| return Ok (std::move (out)); | |
| } | |
| static result<copy_move_counter_t> return_tracked_from_local_with_tag () | |
| { | |
| copy_move_counter_t out (42); | |
| return Ok(out); | |
| } | |
| static hb_result_t<int, copy_move_counter_t> return_tracked_error_from_local () | |
| { | |
| copy_move_counter_t out (42); | |
| return Err (std::move (out)); | |
| } | |
| static hb_result_t<int, copy_move_counter_t> return_tracked_error_from_local_with_tag () | |
| { | |
| copy_move_counter_t out (42); | |
| return Err(out); | |
| } | |
| static result<move_only_val_t> return_move_only_from_local () | |
| { | |
| move_only_val_t out (42); | |
| return Ok (std::move (out)); | |
| } | |
| static void test_return_moves_from_local () | |
| { | |
| // Verify 0 copies and only moves occur when returning local value directly | |
| { | |
| copy_move_counter_t::reset (); | |
| result<copy_move_counter_t> r = return_tracked_from_local (); | |
| hb_always_assert (r.is_ok ()); | |
| hb_always_assert (r.value ().val == 42); | |
| hb_always_assert (copy_move_counter_t::copy_count == 0); | |
| hb_always_assert (copy_move_counter_t::move_count >= 1); | |
| } | |
| // Verify 0 copies and only moves occur when returning local value directly | |
| { | |
| copy_move_counter_t::reset (); | |
| result<copy_move_counter_t> r = return_tracked_from_local_with_tag (); | |
| hb_always_assert (r.is_ok ()); | |
| hb_always_assert (r.value ().val == 42); | |
| hb_always_assert (copy_move_counter_t::copy_count == 0); | |
| hb_always_assert (copy_move_counter_t::move_count >= 1); | |
| } | |
| // Verify 0 copies and only moves occur when returning local error directly | |
| { | |
| copy_move_counter_t::reset (); | |
| hb_result_t<int, copy_move_counter_t> r = return_tracked_error_from_local (); | |
| hb_always_assert (r.is_err ()); | |
| hb_always_assert (r.error().val == 42); | |
| hb_always_assert (copy_move_counter_t::copy_count == 0); | |
| hb_always_assert (copy_move_counter_t::move_count >= 1); | |
| } | |
| // Verify 0 copies and only moves occur when returning local error directly | |
| { | |
| copy_move_counter_t::reset (); | |
| hb_result_t<int, copy_move_counter_t> r = return_tracked_error_from_local_with_tag (); | |
| hb_always_assert (r.is_err ()); | |
| hb_always_assert (r.error().val == 42); | |
| hb_always_assert (copy_move_counter_t::copy_count == 0); | |
| hb_always_assert (copy_move_counter_t::move_count >= 1); | |
| } | |
| // Test returning move-only type (statically proves no copy is attempted) | |
| { | |
| result<move_only_val_t> r = return_move_only_from_local (); | |
| hb_always_assert (r.is_ok ()); | |
| hb_always_assert (r.value ().val == 42); | |
| } | |
| } | |
| struct in_error_t { | |
| bool successful; | |
| in_error_t(bool s) : successful(s) {} | |
| bool in_error() const { | |
| return !successful; | |
| } | |
| }; | |
| static void test_from () { | |
| in_error_t has_error(false); | |
| in_error_t no_error(true); | |
| result<void> r = result<void>::from(has_error, ERR_B); | |
| hb_always_assert(r.is_err()); | |
| hb_always_assert(r.error() == ERR_B); | |
| r = result<void>::from(no_error, ERR_B); | |
| hb_always_assert(!r.is_err()); | |
| } | |
| struct default_constructible_t | |
| { | |
| int val; | |
| default_constructible_t () : val (123) {} | |
| explicit default_constructible_t (int v) : val (v) {} | |
| }; | |
| struct move_only_default_t | |
| { | |
| int val; | |
| move_only_default_t () : val (77) {} | |
| explicit move_only_default_t (int v) : val (v) {} | |
| move_only_default_t (const move_only_default_t&) = delete; | |
| move_only_default_t (move_only_default_t&& o) : val (o.val) { o.val = -1; } | |
| move_only_default_t& operator = (const move_only_default_t&) = delete; | |
| move_only_default_t& operator = (move_only_default_t&& o) { val = o.val; o.val = -1; return *this; } | |
| ~move_only_default_t () = default; | |
| }; | |
| static void test_value_or_default () | |
| { | |
| // Test const & | |
| { | |
| const result<int> r_ok = Ok(42); | |
| hb_always_assert (r_ok.value_or_default () == 42); | |
| const result<int> r_err = Err(ERR_A); | |
| hb_always_assert (r_err.value_or_default () == 0); | |
| } | |
| // Test && (rvalues and move) | |
| { | |
| result<int> r_ok = Ok(42); | |
| hb_always_assert (std::move (r_ok).value_or_default () == 42); | |
| result<int> r_err = Err(ERR_A); | |
| hb_always_assert (std::move (r_err).value_or_default () == 0); | |
| } | |
| // Test custom struct with default constructor | |
| { | |
| result<default_constructible_t> r_ok = default_constructible_t (456); | |
| hb_always_assert (r_ok.value_or_default ().val == 456); | |
| result<default_constructible_t> r_err = ERR_A; | |
| hb_always_assert (r_err.value_or_default ().val == 123); | |
| hb_always_assert (std::move (r_ok).value_or_default ().val == 456); | |
| hb_always_assert (std::move (r_err).value_or_default ().val == 123); | |
| } | |
| // Test pointers | |
| { | |
| int x = 10; | |
| result<int*> r_ok = &x; | |
| hb_always_assert (r_ok.value_or_default () == &x); | |
| result<int*> r_err = ERR_A; | |
| hb_always_assert (r_err.value_or_default () == nullptr); | |
| } | |
| // Test move-only types | |
| { | |
| result<move_only_default_t> r_mo_ok = move_only_default_t (88); | |
| hb_always_assert (std::move (r_mo_ok).value_or_default ().val == 88); | |
| result<move_only_default_t> r_mo_err = ERR_A; | |
| hb_always_assert (std::move (r_mo_err).value_or_default ().val == 77); | |
| } | |
| } | |
| static void test_from_move() | |
| { | |
| { | |
| copy_move_counter_t::reset(); | |
| copy_move_counter_t ok (24); | |
| result<copy_move_counter_t> r_ok = result<copy_move_counter_t>::from(std::move(ok), ERR_B); | |
| hb_always_assert (r_ok.is_ok()); | |
| hb_always_assert (r_ok->val == 24); | |
| hb_always_assert (copy_move_counter_t::copy_count == 0); | |
| hb_always_assert (copy_move_counter_t::move_count == 1); | |
| } | |
| { | |
| copy_move_counter_t::reset(); | |
| copy_move_counter_t fail (-1); | |
| result<copy_move_counter_t> r_fail = result<copy_move_counter_t>::from(std::move(fail), ERR_B); | |
| hb_always_assert (r_fail.is_err()); | |
| hb_always_assert (r_fail.error() == ERR_B); | |
| hb_always_assert (copy_move_counter_t::copy_count == 0); | |
| hb_always_assert (copy_move_counter_t::move_count == 0); | |
| } | |
| } | |
| int main () | |
| { | |
| test_ok_basic (); | |
| test_err_basic (); | |
| test_explicit_ok_err (); | |
| test_pointers (); | |
| test_void (); | |
| test_resource_cleanup (); | |
| test_try_macro (); | |
| test_same_or_convertible_types (); | |
| test_error_interception (); | |
| test_move_only_types (); | |
| test_return_moves_from_local (); | |
| test_from(); | |
| test_value_or_default (); | |
| test_from_move (); | |
| return 0; | |
| } |