Allow expected to be constructed from a convertible value or expected (#1587)

This commit is contained in:
Marco Nilsson 2026-09-07 16:23:20 +02:00 committed by GitHub
parent d5156f9a1a
commit d06cd9f303
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 632 additions and 0 deletions

View File

@ -236,6 +236,72 @@ Since: C++11
--- ---
```cpp
template <typename U>
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 <typename U>
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 <typename U, typename G>
expected(const etl::expected<U, G>& other)
```
**Description**
Copy construct from an etl::expected<U, G> whose value and error types are both implicitly convertible.
Not selected when value_type can be constructed from, or etl::unexpected<error_type> built from, the source etl::expected itself.
Since: C++11
---
```cpp
template <typename U, typename G>
explicit expected(const etl::expected<U, G>& other)
```
**Description**
Copy construct from an etl::expected<U, G> whose value or error type is only explicitly convertible.
Constrained as above.
Since: C++11
---
```cpp
template <typename U, typename G>
expected(etl::expected<U, G>&& other)
```
**Description**
Move construct from an etl::expected<U, G> whose value and error types are both implicitly convertible.
Constrained as above.
Since: C++11
---
```cpp
template <typename U, typename G>
explicit expected(etl::expected<U, G>&& other)
```
**Description**
Move construct from an etl::expected<U, G> whose value or error type is only explicitly convertible.
Constrained as above.
Since: C++11
---
```cpp ```cpp
ETL_CONSTEXPR14 expected(const expected& other) ETL_NOEXCEPT ETL_CONSTEXPR14 expected(const expected& other) ETL_NOEXCEPT
``` ```
@ -353,6 +419,17 @@ Since: C++11
--- ---
```cpp
template <typename U>
expected& operator =(U&& value)
```
**Description**
Assign from a value convertible to value_type.
Constrained like the value constructor.
Since: C++11
---
```cpp ```cpp
expected& operator =(const unexpected_type& error) expected& operator =(const unexpected_type& error)
``` ```

View File

@ -249,6 +249,92 @@ namespace etl
static const unexpect_t unexpect; static const unexpect_t unexpect;
#endif #endif
#if ETL_USING_CPP11
namespace private_expected
{
//*****************************************************************************
// Detects etl::unexpected, which the value constructors must not accept as a value.
//*****************************************************************************
template <typename T>
struct is_unexpected : etl::false_type
{
};
template <typename TError>
struct is_unexpected<etl::unexpected<TError> > : etl::true_type
{
};
//*****************************************************************************
// True when TValue can be built from the source expected, which must be wrapped rather than unwrapped.
//*****************************************************************************
template <typename TValue, typename U, typename G>
struct constructible_from_expected
: etl::integral_constant<
bool, etl::is_constructible<TValue, etl::expected<U, G>&>::value || etl::is_constructible<TValue, etl::expected<U, G> >::value
|| etl::is_constructible<TValue, const etl::expected<U, G>&>::value
|| etl::is_constructible<TValue, const etl::expected<U, G> >::value || etl::is_convertible<etl::expected<U, G>&, TValue>::value
|| etl::is_convertible<etl::expected<U, G>&&, TValue>::value || etl::is_convertible<const etl::expected<U, G>&, TValue>::value
|| etl::is_convertible<const etl::expected<U, G>&&, TValue>::value>
{
};
//*****************************************************************************
// True when TError can be built from the source expected, which must be wrapped rather than unwrapped.
//*****************************************************************************
template <typename TError, typename U, typename G>
struct unexpected_constructible_from_expected
: etl::integral_constant<bool, etl::is_constructible<etl::unexpected<TError>, etl::expected<U, G>&>::value
|| etl::is_constructible<etl::unexpected<TError>, etl::expected<U, G> >::value
|| etl::is_constructible<etl::unexpected<TError>, const etl::expected<U, G>&>::value
|| etl::is_constructible<etl::unexpected<TError>, const etl::expected<U, G> >::value>
{
};
//*****************************************************************************
// Constraints shared by the implicit and explicit value constructors.
//*****************************************************************************
template <typename TValue, typename TError, typename U>
struct is_value_constructor_candidate
: etl::integral_constant<bool, !etl::is_same<typename etl::decay<U>::type, etl::expected<TValue, TError> >::value
&& !etl::is_same<typename etl::decay<U>::type, TValue>::value
&& !etl::is_same<typename etl::decay<U>::type, etl::in_place_t>::value
&& !etl::is_same<typename etl::decay<U>::type, etl::unexpect_t>::value
&& !is_unexpected<typename etl::decay<U>::type>::value && etl::is_constructible<TValue, U&&>::value>
{
};
//*****************************************************************************
// Constraints on the source expected that hold whatever its value category.
//*****************************************************************************
template <typename TValue, typename TError, typename U, typename G>
struct is_expected_conversion_allowed
: etl::integral_constant<bool, !etl::is_same<etl::expected<U, G>, etl::expected<TValue, TError> >::value
&& !constructible_from_expected<TValue, U, G>::value
&& !unexpected_constructible_from_expected<TError, U, G>::value>
{
};
//*****************************************************************************
// UArgument and GArgument are the source members as the constructor receives them.
//*****************************************************************************
template <typename TValue, typename TError, typename UArgument, typename GArgument>
struct is_expected_conversion_constructible
: etl::integral_constant<bool, etl::is_constructible<TValue, UArgument>::value && etl::is_constructible<TError, GArgument>::value>
{
};
//*****************************************************************************
// A converting constructor is implicit only when both the value and the error convert implicitly.
//*****************************************************************************
template <typename TValue, typename TError, typename UArgument, typename GArgument>
struct is_expected_conversion_implicit
: etl::integral_constant<bool, etl::is_convertible<UArgument, TValue>::value && etl::is_convertible<GArgument, TError>::value>
{
};
} // namespace private_expected
#endif
//***************************************************************************** //*****************************************************************************
/// Expected type. /// Expected type.
//***************************************************************************** //*****************************************************************************
@ -293,6 +379,114 @@ namespace etl
} }
#endif #endif
#if ETL_USING_CPP11
//*******************************************
/// Constructor from a value convertible to value_type.
//*******************************************
template <typename U,
typename etl::enable_if<
private_expected::is_value_constructor_candidate<TValue, TError, U>::value && etl::is_convertible<U&&, TValue>::value, int>::type = 0>
ETL_CONSTEXPR14 expected(U&& value_)
: storage(etl::in_place_index_t<Value_Type>(), etl::forward<U>(value_))
{
}
//*******************************************
/// Constructor from a value explicitly convertible to value_type.
//*******************************************
template <typename U, typename etl::enable_if<private_expected::is_value_constructor_candidate<TValue, TError, U>::value
&& !etl::is_convertible<U&&, TValue>::value,
int>::type = 0>
ETL_CONSTEXPR14 explicit expected(U&& value_)
: storage(etl::in_place_index_t<Value_Type>(), etl::forward<U>(value_))
{
}
//*******************************************
/// Copy constructor from an expected with convertible value and error types.
//*******************************************
template <typename U, typename G,
typename etl::enable_if<private_expected::is_expected_conversion_allowed<TValue, TError, U, G>::value
&& private_expected::is_expected_conversion_constructible<TValue, TError, const U&, const G&>::value
&& private_expected::is_expected_conversion_implicit<TValue, TError, const U&, const G&>::value,
int>::type = 0>
expected(const etl::expected<U, G>& other)
: storage(private_variant::valueless_t())
{
if (other.has_value())
{
storage.template emplace<Value_Type>(other.value());
}
else
{
storage.template emplace<Error_Type>(other.error());
}
}
//*******************************************
/// Copy constructor from an expected with explicitly convertible value or error types.
//*******************************************
template <typename U, typename G,
typename etl::enable_if<private_expected::is_expected_conversion_allowed<TValue, TError, U, G>::value
&& private_expected::is_expected_conversion_constructible<TValue, TError, const U&, const G&>::value
&& !private_expected::is_expected_conversion_implicit<TValue, TError, const U&, const G&>::value,
int>::type = 0>
explicit expected(const etl::expected<U, G>& other)
: storage(private_variant::valueless_t())
{
if (other.has_value())
{
storage.template emplace<Value_Type>(other.value());
}
else
{
storage.template emplace<Error_Type>(other.error());
}
}
//*******************************************
/// Move constructor from an expected with convertible value and error types.
//*******************************************
template <typename U, typename G,
typename etl::enable_if<private_expected::is_expected_conversion_allowed<TValue, TError, U, G>::value
&& private_expected::is_expected_conversion_constructible<TValue, TError, U&&, G&&>::value
&& private_expected::is_expected_conversion_implicit<TValue, TError, U&&, G&&>::value,
int>::type = 0>
expected(etl::expected<U, G>&& other)
: storage(private_variant::valueless_t())
{
if (other.has_value())
{
storage.template emplace<Value_Type>(etl::move(other.value()));
}
else
{
storage.template emplace<Error_Type>(etl::move(other.error()));
}
}
//*******************************************
/// Move constructor from an expected with explicitly convertible value or error types.
//*******************************************
template <typename U, typename G,
typename etl::enable_if<private_expected::is_expected_conversion_allowed<TValue, TError, U, G>::value
&& private_expected::is_expected_conversion_constructible<TValue, TError, U&&, G&&>::value
&& !private_expected::is_expected_conversion_implicit<TValue, TError, U&&, G&&>::value,
int>::type = 0>
explicit expected(etl::expected<U, G>&& other)
: storage(private_variant::valueless_t())
{
if (other.has_value())
{
storage.template emplace<Value_Type>(etl::move(other.value()));
}
else
{
storage.template emplace<Error_Type>(etl::move(other.error()));
}
}
#endif
//******************************************* //*******************************************
/// Copy constructor /// Copy constructor
//******************************************* //*******************************************
@ -451,6 +645,17 @@ namespace etl
return *this; return *this;
} }
//*******************************************
/// Assign from a value convertible to value_type.
//*******************************************
template <typename U, typename etl::enable_if<private_expected::is_value_constructor_candidate<TValue, TError, U>::value, int>::type = 0>
expected& operator=(U&& value)
{
storage.template emplace<Value_Type>(etl::forward<U>(value));
return *this;
}
#endif #endif
//******************************************* //*******************************************

View File

@ -69,6 +69,14 @@ namespace etl
{ {
namespace private_variant 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. /// Switch-based dispatch for destroy/copy/move.
/// Replaces the per-instance function pointer with /// Replaces the per-instance function pointer with
@ -1034,6 +1042,16 @@ namespace etl
} }
#include "diagnostic_pop.h" #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. /// Construct from arguments.
//*************************************************************************** //***************************************************************************

View File

@ -120,6 +120,147 @@ namespace
std::string e; 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<int, int>&) {}
};
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<Value, Error>; using Expected = etl::expected<Value, Error>;
using ExpectedV = etl::expected<void, Error>; using ExpectedV = etl::expected<void, Error>;
using ExpectedM = etl::expected<ValueM, ErrorM>; using ExpectedM = etl::expected<ValueM, ErrorM>;
@ -1651,5 +1792,196 @@ namespace
CHECK_FALSE(result.has_value()); CHECK_FALSE(result.has_value());
CHECK_EQUAL("original_transformed", result.error().e); 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<std::string, std::string> 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<std::string, std::string> source(etl::unexpected<std::string>(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<int, Error>&)
: wrapped(true)
{
}
explicit WrapsExpected(int)
: wrapped(false)
{
}
bool wrapped;
};
const etl::expected<int, Error> source(7);
const etl::expected<WrapsExpected, Error> result(source);
CHECK_TRUE(result.has_value());
CHECK_TRUE(result.value().wrapped);
}
//*************************************************************************
TEST(test_explicit_value_conversion_is_not_implicit)
{
using ExplicitExpected = etl::expected<ExplicitValue, Error>;
CHECK_TRUE((etl::is_constructible<ExplicitExpected, std::string>::value));
CHECK_FALSE((etl::is_convertible<std::string, ExplicitExpected>::value));
CHECK_TRUE((etl::is_convertible<std::string, Expected>::value));
}
//*************************************************************************
TEST(test_explicit_value_conversion_is_available_by_direct_initialisation)
{
const etl::expected<ExplicitValue, Error> 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<std::string, std::string>;
using ExplicitExpected = etl::expected<Value, ExplicitError>;
CHECK_TRUE((etl::is_constructible<ExplicitExpected, const Source&>::value));
CHECK_FALSE((etl::is_convertible<const Source&, ExplicitExpected>::value));
CHECK_TRUE((etl::is_convertible<const Source&, Expected>::value));
}
//*************************************************************************
TEST(test_explicit_error_conversion_carries_the_error)
{
const etl::expected<std::string, std::string> source(etl::unexpected<std::string>(std::string("failed")));
const etl::expected<Value, ExplicitError> result(source);
CHECK_FALSE(result.has_value());
CHECK_EQUAL("failed", result.error().e);
}
//*************************************************************************
TEST(test_move_constructor_from_expected_with_convertible_types)
{
etl::expected<MoveOnlyValue, Error> source(MoveOnlyValue(1));
const etl::expected<FromMoveOnlyValue, Error> 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<MoveOnlyValue, Error> source(etl::unexpected<Error>(Error("failed")));
const etl::expected<FromMoveOnlyValue, Error> 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<NoMoveValue, Error> 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<int, int>;
CHECK_FALSE((etl::is_constructible<etl::expected<FromInt, ErrorFromExpected>, const Source&>::value));
CHECK_FALSE((etl::is_constructible<etl::expected<FromInt, ErrorFromExpected>, Source&&>::value));
CHECK_TRUE((etl::is_constructible<etl::expected<FromInt, FromInt>, 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<int, Error> source(7);
const etl::expected<ImmobileValue, Error> 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<int, int> source(etl::unexpected<int>(9));
const etl::expected<FromInt, ImmobileError> result(source);
CHECK_FALSE(result.has_value());
CHECK_EQUAL(9, result.error().e);
}
CHECK_EQUAL(1, ImmobileError::destructions);
}
} }
} // namespace } // namespace