diff --git a/docs/utilities/expected.md b/docs/utilities/expected.md index 8443a782..ab021424 100644 --- a/docs/utilities/expected.md +++ b/docs/utilities/expected.md @@ -236,6 +236,72 @@ Since: C++11 --- +```cpp +template +ETL_CONSTEXPR14 expected(U&& value) +``` +**Description** +Construct the value type from a value implicitly convertible to it. +Not selected when U is an etl::expected, etl::unexpected, etl::in_place_t, etl::unexpect_t or value_type itself. +Since: C++11 + +--- + +```cpp +template +ETL_CONSTEXPR14 explicit expected(U&& value) +``` +**Description** +Construct the value type from a value that is only explicitly convertible to it. +Constrained as above. +Since: C++11 + +--- + +```cpp +template +expected(const etl::expected& other) +``` +**Description** +Copy construct from an etl::expected whose value and error types are both implicitly convertible. +Not selected when value_type can be constructed from, or etl::unexpected built from, the source etl::expected itself. +Since: C++11 + +--- + +```cpp +template +explicit expected(const etl::expected& other) +``` +**Description** +Copy construct from an etl::expected whose value or error type is only explicitly convertible. +Constrained as above. +Since: C++11 + +--- + +```cpp +template +expected(etl::expected&& other) +``` +**Description** +Move construct from an etl::expected whose value and error types are both implicitly convertible. +Constrained as above. +Since: C++11 + +--- + +```cpp +template +explicit expected(etl::expected&& other) +``` +**Description** +Move construct from an etl::expected whose value or error type is only explicitly convertible. +Constrained as above. +Since: C++11 + +--- + ```cpp ETL_CONSTEXPR14 expected(const expected& other) ETL_NOEXCEPT ``` @@ -353,6 +419,17 @@ Since: C++11 --- +```cpp +template +expected& operator =(U&& value) +``` +**Description** +Assign from a value convertible to value_type. +Constrained like the value constructor. +Since: C++11 + +--- + ```cpp expected& operator =(const unexpected_type& error) ``` diff --git a/include/etl/expected.h b/include/etl/expected.h index 49b99aad..092434dc 100644 --- a/include/etl/expected.h +++ b/include/etl/expected.h @@ -249,6 +249,92 @@ namespace etl static const unexpect_t unexpect; #endif +#if ETL_USING_CPP11 + namespace private_expected + { + //***************************************************************************** + // Detects etl::unexpected, which the value constructors must not accept as a value. + //***************************************************************************** + template + struct is_unexpected : etl::false_type + { + }; + + template + struct is_unexpected > : etl::true_type + { + }; + + //***************************************************************************** + // True when TValue can be built from the source expected, which must be wrapped rather than unwrapped. + //***************************************************************************** + template + struct constructible_from_expected + : etl::integral_constant< + bool, etl::is_constructible&>::value || etl::is_constructible >::value + || etl::is_constructible&>::value + || etl::is_constructible >::value || etl::is_convertible&, TValue>::value + || etl::is_convertible&&, TValue>::value || etl::is_convertible&, TValue>::value + || etl::is_convertible&&, TValue>::value> + { + }; + + //***************************************************************************** + // True when TError can be built from the source expected, which must be wrapped rather than unwrapped. + //***************************************************************************** + template + struct unexpected_constructible_from_expected + : etl::integral_constant, etl::expected&>::value + || etl::is_constructible, etl::expected >::value + || etl::is_constructible, const etl::expected&>::value + || etl::is_constructible, const etl::expected >::value> + { + }; + + //***************************************************************************** + // Constraints shared by the implicit and explicit value constructors. + //***************************************************************************** + template + struct is_value_constructor_candidate + : etl::integral_constant::type, etl::expected >::value + && !etl::is_same::type, TValue>::value + && !etl::is_same::type, etl::in_place_t>::value + && !etl::is_same::type, etl::unexpect_t>::value + && !is_unexpected::type>::value && etl::is_constructible::value> + { + }; + + //***************************************************************************** + // Constraints on the source expected that hold whatever its value category. + //***************************************************************************** + template + struct is_expected_conversion_allowed + : etl::integral_constant, etl::expected >::value + && !constructible_from_expected::value + && !unexpected_constructible_from_expected::value> + { + }; + + //***************************************************************************** + // UArgument and GArgument are the source members as the constructor receives them. + //***************************************************************************** + template + struct is_expected_conversion_constructible + : etl::integral_constant::value && etl::is_constructible::value> + { + }; + + //***************************************************************************** + // A converting constructor is implicit only when both the value and the error convert implicitly. + //***************************************************************************** + template + struct is_expected_conversion_implicit + : etl::integral_constant::value && etl::is_convertible::value> + { + }; + } // namespace private_expected +#endif + //***************************************************************************** /// Expected type. //***************************************************************************** @@ -293,6 +379,114 @@ namespace etl } #endif +#if ETL_USING_CPP11 + //******************************************* + /// Constructor from a value convertible to value_type. + //******************************************* + template ::value && etl::is_convertible::value, int>::type = 0> + ETL_CONSTEXPR14 expected(U&& value_) + : storage(etl::in_place_index_t(), etl::forward(value_)) + { + } + + //******************************************* + /// Constructor from a value explicitly convertible to value_type. + //******************************************* + template ::value + && !etl::is_convertible::value, + int>::type = 0> + ETL_CONSTEXPR14 explicit expected(U&& value_) + : storage(etl::in_place_index_t(), etl::forward(value_)) + { + } + + //******************************************* + /// Copy constructor from an expected with convertible value and error types. + //******************************************* + template ::value + && private_expected::is_expected_conversion_constructible::value + && private_expected::is_expected_conversion_implicit::value, + int>::type = 0> + expected(const etl::expected& other) + : storage(private_variant::valueless_t()) + { + if (other.has_value()) + { + storage.template emplace(other.value()); + } + else + { + storage.template emplace(other.error()); + } + } + + //******************************************* + /// Copy constructor from an expected with explicitly convertible value or error types. + //******************************************* + template ::value + && private_expected::is_expected_conversion_constructible::value + && !private_expected::is_expected_conversion_implicit::value, + int>::type = 0> + explicit expected(const etl::expected& other) + : storage(private_variant::valueless_t()) + { + if (other.has_value()) + { + storage.template emplace(other.value()); + } + else + { + storage.template emplace(other.error()); + } + } + + //******************************************* + /// Move constructor from an expected with convertible value and error types. + //******************************************* + template ::value + && private_expected::is_expected_conversion_constructible::value + && private_expected::is_expected_conversion_implicit::value, + int>::type = 0> + expected(etl::expected&& other) + : storage(private_variant::valueless_t()) + { + if (other.has_value()) + { + storage.template emplace(etl::move(other.value())); + } + else + { + storage.template emplace(etl::move(other.error())); + } + } + + //******************************************* + /// Move constructor from an expected with explicitly convertible value or error types. + //******************************************* + template ::value + && private_expected::is_expected_conversion_constructible::value + && !private_expected::is_expected_conversion_implicit::value, + int>::type = 0> + explicit expected(etl::expected&& other) + : storage(private_variant::valueless_t()) + { + if (other.has_value()) + { + storage.template emplace(etl::move(other.value())); + } + else + { + storage.template emplace(etl::move(other.error())); + } + } +#endif + //******************************************* /// Copy constructor //******************************************* @@ -451,6 +645,17 @@ namespace etl return *this; } + + //******************************************* + /// Assign from a value convertible to value_type. + //******************************************* + template ::value, int>::type = 0> + expected& operator=(U&& value) + { + storage.template emplace(etl::forward(value)); + + return *this; + } #endif //******************************************* diff --git a/include/etl/private/variant_variadic.h b/include/etl/private/variant_variadic.h index 924d73a3..4161aa31 100644 --- a/include/etl/private/variant_variadic.h +++ b/include/etl/private/variant_variadic.h @@ -69,6 +69,14 @@ namespace etl { namespace private_variant { + //******************************************* + /// Tag for constructing a variant with no alternative. + //******************************************* + struct valueless_t + { + explicit ETL_CONSTEXPR valueless_t() {} + }; + //******************************************* /// Switch-based dispatch for destroy/copy/move. /// Replaces the per-instance function pointer with @@ -1034,6 +1042,16 @@ namespace etl } #include "diagnostic_pop.h" + //*************************************************************************** + /// Construct with no alternative. + /// The caller is expected to emplace one; until it does the variant is valueless. + /// Lets a caller choose the alternative at run time without a temporary variant. + //*************************************************************************** + ETL_CONSTEXPR14 explicit variant(private_variant::valueless_t) ETL_NOEXCEPT + : base_type() + { + } + //*************************************************************************** /// Construct from arguments. //*************************************************************************** diff --git a/test/test_expected.cpp b/test/test_expected.cpp index b7bee13d..d0027f08 100644 --- a/test/test_expected.cpp +++ b/test/test_expected.cpp @@ -120,6 +120,147 @@ namespace std::string e; }; + struct ExplicitValue + { + std::string v; + + ExplicitValue() {} + + explicit ExplicitValue(const std::string& v_) + : v(v_) + { + } + }; + + struct ExplicitError + { + std::string e; + + ExplicitError() = default; + + explicit ExplicitError(const std::string& e_) + : e(e_) + { + } + }; + + struct MoveOnlyValue + { + MoveOnlyValue() + : v(0) + { + } + + explicit MoveOnlyValue(int v_) + : v(v_) + { + } + + MoveOnlyValue(MoveOnlyValue&&) = default; + MoveOnlyValue& operator=(MoveOnlyValue&&) = default; + + MoveOnlyValue(const MoveOnlyValue&) = delete; + MoveOnlyValue& operator=(const MoveOnlyValue&) = delete; + + int v; + }; + + struct FromMoveOnlyValue + { + FromMoveOnlyValue() + : v(0) + { + } + + FromMoveOnlyValue(MoveOnlyValue&& other) + : v(other.v) + { + } + + int v; + }; + + struct NoMoveValue + { + explicit NoMoveValue(int v_) + : v(v_) + { + } + + NoMoveValue(const NoMoveValue&) = default; + NoMoveValue(NoMoveValue&&) = delete; + + int v; + }; + + struct FromInt + { + FromInt() + : v(0) + { + } + + FromInt(int v_) + : v(v_) + { + } + + int v; + }; + + struct ErrorFromExpected + { + ErrorFromExpected() {} + + ErrorFromExpected(int) {} + + ErrorFromExpected(const etl::expected&) {} + }; + + struct ImmobileValue + { + static int destructions; + + explicit ImmobileValue(int v_) + : v(v_) + { + } + + ImmobileValue(const ImmobileValue&) = delete; + ImmobileValue(ImmobileValue&&) = delete; + + ~ImmobileValue() + { + ++destructions; + } + + int v; + }; + + int ImmobileValue::destructions = 0; + + struct ImmobileError + { + static int destructions; + + ImmobileError(int e_) + : e(e_) + { + } + + ImmobileError(const ImmobileError&) = delete; + ImmobileError(ImmobileError&&) = delete; + + ~ImmobileError() + { + ++destructions; + } + + int e; + }; + + int ImmobileError::destructions = 0; + using Expected = etl::expected; using ExpectedV = etl::expected; using ExpectedM = etl::expected; @@ -1651,5 +1792,196 @@ namespace CHECK_FALSE(result.has_value()); CHECK_EQUAL("original_transformed", result.error().e); } + + //************************************************************************* + TEST(test_constructor_from_value_convertible_to_value_type) + { + const Expected result = std::string("converted"); + + CHECK_TRUE(result.has_value()); + CHECK_EQUAL("converted", result.value().v); + } + + //************************************************************************* + TEST(test_constructor_from_expected_with_convertible_types) + { + const etl::expected source(std::string("converted")); + + const Expected result(source); + + CHECK_TRUE(result.has_value()); + CHECK_EQUAL("converted", result.value().v); + } + + //************************************************************************* + TEST(test_constructor_from_expected_with_convertible_types_carries_error) + { + const etl::expected source(etl::unexpected(std::string("failed"))); + + const Expected result(source); + + CHECK_FALSE(result.has_value()); + CHECK_EQUAL("failed", result.error().e); + } + + //************************************************************************* + TEST(test_value_type_constructible_from_expected_is_not_unwrapped) + { + struct WrapsExpected + { + WrapsExpected() + : wrapped(false) + { + } + + WrapsExpected(const etl::expected&) + : wrapped(true) + { + } + + explicit WrapsExpected(int) + : wrapped(false) + { + } + + bool wrapped; + }; + + const etl::expected source(7); + + const etl::expected result(source); + + CHECK_TRUE(result.has_value()); + CHECK_TRUE(result.value().wrapped); + } + + //************************************************************************* + TEST(test_explicit_value_conversion_is_not_implicit) + { + using ExplicitExpected = etl::expected; + + CHECK_TRUE((etl::is_constructible::value)); + CHECK_FALSE((etl::is_convertible::value)); + + CHECK_TRUE((etl::is_convertible::value)); + } + + //************************************************************************* + TEST(test_explicit_value_conversion_is_available_by_direct_initialisation) + { + const etl::expected result(std::string("converted")); + + CHECK_TRUE(result.has_value()); + CHECK_EQUAL("converted", result.value().v); + } + + //************************************************************************* + TEST(test_explicit_error_conversion_is_not_implicit) + { + using Source = etl::expected; + using ExplicitExpected = etl::expected; + + CHECK_TRUE((etl::is_constructible::value)); + CHECK_FALSE((etl::is_convertible::value)); + + CHECK_TRUE((etl::is_convertible::value)); + } + + //************************************************************************* + TEST(test_explicit_error_conversion_carries_the_error) + { + const etl::expected source(etl::unexpected(std::string("failed"))); + + const etl::expected result(source); + + CHECK_FALSE(result.has_value()); + CHECK_EQUAL("failed", result.error().e); + } + + //************************************************************************* + TEST(test_move_constructor_from_expected_with_convertible_types) + { + etl::expected source(MoveOnlyValue(1)); + + const etl::expected result(etl::move(source)); + + CHECK_TRUE(result.has_value()); + CHECK_EQUAL(1, result.value().v); + } + + //************************************************************************* + TEST(test_move_constructor_from_expected_with_convertible_types_carries_error) + { + etl::expected source(etl::unexpected(Error("failed"))); + + const etl::expected result(etl::move(source)); + + CHECK_FALSE(result.has_value()); + CHECK_EQUAL("failed", result.error().e); + } + + //************************************************************************* + TEST(test_constructor_from_value_does_not_require_a_move) + { + const etl::expected result(1); + + CHECK_TRUE(result.has_value()); + CHECK_EQUAL(1, result.value().v); + } + + //************************************************************************* + TEST(test_error_type_constructible_from_expected_is_not_converted) + { + using Source = etl::expected; + + CHECK_FALSE((etl::is_constructible, const Source&>::value)); + CHECK_FALSE((etl::is_constructible, Source&&>::value)); + + CHECK_TRUE((etl::is_constructible, const Source&>::value)); + } + + //************************************************************************* + TEST(test_assign_from_value_convertible_to_value_type) + { + Expected result; + + result = std::string("converted"); + + CHECK_TRUE(result.has_value()); + CHECK_EQUAL("converted", result.value().v); + } + //************************************************************************* + TEST(test_conversion_into_an_immobile_value_constructs_in_place) + { + ImmobileValue::destructions = 0; + + { + const etl::expected source(7); + + const etl::expected result(source); + + CHECK_TRUE(result.has_value()); + CHECK_EQUAL(7, result.value().v); + } + + CHECK_EQUAL(1, ImmobileValue::destructions); + } + + //************************************************************************* + TEST(test_conversion_into_an_immobile_error_constructs_in_place) + { + ImmobileError::destructions = 0; + + { + const etl::expected source(etl::unexpected(9)); + + const etl::expected result(source); + + CHECK_FALSE(result.has_value()); + CHECK_EQUAL(9, result.error().e); + } + + CHECK_EQUAL(1, ImmobileError::destructions); + } } } // namespace