mirror of
https://github.com/intel/llvm.git
synced 2026-01-20 01:58:44 +08:00
The calls to std::construct_at might overwrite the previously set __has_value_ flag in the case where the flag is overlapping with the actual value or error being stored (since we use [[no_unique_address]]). To fix this issue, this patch ensures that we initialize the __has_value_ flag after we call std::construct_at. Fixes #68552
This commit is contained in:
@@ -119,9 +119,7 @@ public:
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr expected()
|
||||
noexcept(is_nothrow_default_constructible_v<_Tp>) // strengthened
|
||||
requires is_default_constructible_v<_Tp>
|
||||
: __has_val_(true) {
|
||||
std::construct_at(std::addressof(__union_.__val_));
|
||||
}
|
||||
: __union_(std::in_place), __has_val_(true) {}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr expected(const expected&) = delete;
|
||||
|
||||
@@ -136,14 +134,7 @@ public:
|
||||
noexcept(is_nothrow_copy_constructible_v<_Tp> && is_nothrow_copy_constructible_v<_Err>) // strengthened
|
||||
requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err> &&
|
||||
!(is_trivially_copy_constructible_v<_Tp> && is_trivially_copy_constructible_v<_Err>))
|
||||
: __has_val_(__other.__has_val_) {
|
||||
if (__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__val_), __other.__union_.__val_);
|
||||
} else {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __other.__union_.__unex_);
|
||||
}
|
||||
}
|
||||
|
||||
: __union_(__other.__has_val_, __other.__union_), __has_val_(__other.__has_val_) { }
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
|
||||
requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err>
|
||||
@@ -154,13 +145,7 @@ public:
|
||||
noexcept(is_nothrow_move_constructible_v<_Tp> && is_nothrow_move_constructible_v<_Err>)
|
||||
requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err> &&
|
||||
!(is_trivially_move_constructible_v<_Tp> && is_trivially_move_constructible_v<_Err>))
|
||||
: __has_val_(__other.__has_val_) {
|
||||
if (__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__val_), std::move(__other.__union_.__val_));
|
||||
} else {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::move(__other.__union_.__unex_));
|
||||
}
|
||||
}
|
||||
: __union_(__other.__has_val_, std::move(__other.__union_)), __has_val_(__other.__has_val_) { }
|
||||
|
||||
private:
|
||||
template <class _Up, class _OtherErr, class _UfQual, class _OtherErrQual>
|
||||
@@ -200,26 +185,14 @@ public:
|
||||
expected(const expected<_Up, _OtherErr>& __other)
|
||||
noexcept(is_nothrow_constructible_v<_Tp, const _Up&> &&
|
||||
is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
|
||||
: __has_val_(__other.__has_val_) {
|
||||
if (__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__val_), __other.__union_.__val_);
|
||||
} else {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __other.__union_.__unex_);
|
||||
}
|
||||
}
|
||||
: __union_(__other.__has_val_, __other.__union_), __has_val_(__other.__has_val_) {}
|
||||
|
||||
template <class _Up, class _OtherErr>
|
||||
requires __can_convert<_Up, _OtherErr, _Up, _OtherErr>::value
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp> || !is_convertible_v<_OtherErr, _Err>)
|
||||
expected(expected<_Up, _OtherErr>&& __other)
|
||||
noexcept(is_nothrow_constructible_v<_Tp, _Up> && is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
|
||||
: __has_val_(__other.__has_val_) {
|
||||
if (__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__val_), std::move(__other.__union_.__val_));
|
||||
} else {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::move(__other.__union_.__unex_));
|
||||
}
|
||||
}
|
||||
: __union_(__other.__has_val_, std::move(__other.__union_)), __has_val_(__other.__has_val_) {}
|
||||
|
||||
template <class _Up = _Tp>
|
||||
requires(!is_same_v<remove_cvref_t<_Up>, in_place_t> && !is_same_v<expected, remove_cvref_t<_Up>> &&
|
||||
@@ -227,61 +200,47 @@ public:
|
||||
(!is_same_v<remove_cv_t<_Tp>, bool> || !__is_std_expected<remove_cvref_t<_Up>>::value))
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp>)
|
||||
expected(_Up&& __u) noexcept(is_nothrow_constructible_v<_Tp, _Up>) // strengthened
|
||||
: __has_val_(true) {
|
||||
std::construct_at(std::addressof(__union_.__val_), std::forward<_Up>(__u));
|
||||
}
|
||||
: __union_(std::in_place, std::forward<_Up>(__u)), __has_val_(true) {}
|
||||
|
||||
template <class _OtherErr>
|
||||
requires is_constructible_v<_Err, const _OtherErr&>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
|
||||
expected(const unexpected<_OtherErr>& __unex)
|
||||
noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __unex.error());
|
||||
}
|
||||
: __union_(std::unexpect, __unex.error()), __has_val_(false) {}
|
||||
|
||||
template <class _OtherErr>
|
||||
requires is_constructible_v<_Err, _OtherErr>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
|
||||
expected(unexpected<_OtherErr>&& __unex)
|
||||
noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::move(__unex.error()));
|
||||
}
|
||||
: __union_(std::unexpect, std::move(__unex.error())), __has_val_(false) {}
|
||||
|
||||
template <class... _Args>
|
||||
requires is_constructible_v<_Tp, _Args...>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t, _Args&&... __args)
|
||||
noexcept(is_nothrow_constructible_v<_Tp, _Args...>) // strengthened
|
||||
: __has_val_(true) {
|
||||
std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...);
|
||||
}
|
||||
: __union_(std::in_place, std::forward<_Args>(__args)...), __has_val_(true) {}
|
||||
|
||||
template <class _Up, class... _Args>
|
||||
requires is_constructible_v< _Tp, initializer_list<_Up>&, _Args... >
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit
|
||||
expected(in_place_t, initializer_list<_Up> __il, _Args&&... __args)
|
||||
noexcept(is_nothrow_constructible_v<_Tp, initializer_list<_Up>&, _Args...>) // strengthened
|
||||
: __has_val_(true) {
|
||||
std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...);
|
||||
}
|
||||
: __union_(std::in_place, __il, std::forward<_Args>(__args)...), __has_val_(true) {}
|
||||
|
||||
template <class... _Args>
|
||||
requires is_constructible_v<_Err, _Args...>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args)
|
||||
noexcept(is_nothrow_constructible_v<_Err, _Args...>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::forward<_Args>(__args)...);
|
||||
}
|
||||
noexcept(is_nothrow_constructible_v<_Err, _Args...>) // strengthened
|
||||
: __union_(std::unexpect, std::forward<_Args>(__args)...), __has_val_(false) {}
|
||||
|
||||
template <class _Up, class... _Args>
|
||||
requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit
|
||||
expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args)
|
||||
noexcept(is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __il, std::forward<_Args>(__args)...);
|
||||
}
|
||||
: __union_(std::unexpect, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
|
||||
|
||||
// [expected.object.dtor], destructor
|
||||
|
||||
@@ -440,9 +399,10 @@ public:
|
||||
std::destroy_at(std::addressof(__union_.__val_));
|
||||
} else {
|
||||
std::destroy_at(std::addressof(__union_.__unex_));
|
||||
__has_val_ = true;
|
||||
}
|
||||
return *std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...);
|
||||
std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...);
|
||||
__has_val_ = true;
|
||||
return __union_.__val_;
|
||||
}
|
||||
|
||||
template <class _Up, class... _Args>
|
||||
@@ -452,9 +412,10 @@ public:
|
||||
std::destroy_at(std::addressof(__union_.__val_));
|
||||
} else {
|
||||
std::destroy_at(std::addressof(__union_.__unex_));
|
||||
__has_val_ = true;
|
||||
}
|
||||
return *std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...);
|
||||
std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...);
|
||||
__has_val_ = true;
|
||||
return __union_.__val_;
|
||||
}
|
||||
|
||||
|
||||
@@ -893,11 +854,15 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
struct __empty_t {};
|
||||
|
||||
template <class _ValueType, class _ErrorType>
|
||||
union __union_t {
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr __union_t() {}
|
||||
template <class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::in_place_t, _Args&&... __args)
|
||||
: __val_(std::forward<_Args>(__args)...) {}
|
||||
|
||||
template <class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
|
||||
: __unex_(std::forward<_Args>(__args)...) {}
|
||||
|
||||
template <class _Func, class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
|
||||
@@ -909,6 +874,14 @@ private:
|
||||
std::__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
|
||||
: __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
|
||||
|
||||
template <class _Union>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
|
||||
if (__has_val)
|
||||
std::construct_at(std::addressof(__val_), std::forward<_Union>(__other).__val_);
|
||||
else
|
||||
std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
|
||||
}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
|
||||
requires(is_trivially_destructible_v<_ValueType> && is_trivially_destructible_v<_ErrorType>)
|
||||
= default;
|
||||
@@ -927,10 +900,17 @@ private:
|
||||
template <class _ValueType, class _ErrorType>
|
||||
requires(is_trivially_move_constructible_v<_ValueType> && is_trivially_move_constructible_v<_ErrorType>)
|
||||
union __union_t<_ValueType, _ErrorType> {
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {}
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = default;
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
|
||||
|
||||
template <class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::in_place_t, _Args&&... __args)
|
||||
: __val_(std::forward<_Args>(__args)...) {}
|
||||
|
||||
template <class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
|
||||
: __unex_(std::forward<_Args>(__args)...) {}
|
||||
|
||||
template <class _Func, class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
|
||||
std::__expected_construct_in_place_from_invoke_tag, _Func&& __f, _Args&&... __args)
|
||||
@@ -941,6 +921,14 @@ private:
|
||||
std::__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
|
||||
: __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
|
||||
|
||||
template <class _Union>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
|
||||
if (__has_val)
|
||||
std::construct_at(std::addressof(__val_), std::forward<_Union>(__other).__val_);
|
||||
else
|
||||
std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
|
||||
}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
|
||||
requires(is_trivially_destructible_v<_ValueType> && is_trivially_destructible_v<_ErrorType>)
|
||||
= default;
|
||||
@@ -950,7 +938,6 @@ private:
|
||||
requires(!is_trivially_destructible_v<_ValueType> || !is_trivially_destructible_v<_ErrorType>)
|
||||
{}
|
||||
|
||||
_LIBCPP_NO_UNIQUE_ADDRESS __empty_t __empty_;
|
||||
_LIBCPP_NO_UNIQUE_ADDRESS _ValueType __val_;
|
||||
_LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
|
||||
};
|
||||
@@ -998,11 +985,7 @@ public:
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr expected(const expected& __rhs)
|
||||
noexcept(is_nothrow_copy_constructible_v<_Err>) // strengthened
|
||||
requires(is_copy_constructible_v<_Err> && !is_trivially_copy_constructible_v<_Err>)
|
||||
: __has_val_(__rhs.__has_val_) {
|
||||
if (!__rhs.__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __rhs.__union_.__unex_);
|
||||
}
|
||||
}
|
||||
: __union_(__rhs.__has_val_, __rhs.__union_), __has_val_(__rhs.__has_val_) {}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
|
||||
requires(is_move_constructible_v<_Err> && is_trivially_move_constructible_v<_Err>)
|
||||
@@ -1011,51 +994,35 @@ public:
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr expected(expected&& __rhs)
|
||||
noexcept(is_nothrow_move_constructible_v<_Err>)
|
||||
requires(is_move_constructible_v<_Err> && !is_trivially_move_constructible_v<_Err>)
|
||||
: __has_val_(__rhs.__has_val_) {
|
||||
if (!__rhs.__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::move(__rhs.__union_.__unex_));
|
||||
}
|
||||
}
|
||||
: __union_(__rhs.__has_val_, std::move(__rhs.__union_)), __has_val_(__rhs.__has_val_) {}
|
||||
|
||||
template <class _Up, class _OtherErr>
|
||||
requires __can_convert<_Up, _OtherErr, const _OtherErr&>::value
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
|
||||
expected(const expected<_Up, _OtherErr>& __rhs)
|
||||
noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
|
||||
: __has_val_(__rhs.__has_val_) {
|
||||
if (!__rhs.__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __rhs.__union_.__unex_);
|
||||
}
|
||||
}
|
||||
: __union_(__rhs.__has_val_, __rhs.__union_), __has_val_(__rhs.__has_val_) {}
|
||||
|
||||
template <class _Up, class _OtherErr>
|
||||
requires __can_convert<_Up, _OtherErr, _OtherErr>::value
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
|
||||
expected(expected<_Up, _OtherErr>&& __rhs)
|
||||
noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
|
||||
: __has_val_(__rhs.__has_val_) {
|
||||
if (!__rhs.__has_val_) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::move(__rhs.__union_.__unex_));
|
||||
}
|
||||
}
|
||||
: __union_(__rhs.__has_val_, std::move(__rhs.__union_)), __has_val_(__rhs.__has_val_) {}
|
||||
|
||||
template <class _OtherErr>
|
||||
requires is_constructible_v<_Err, const _OtherErr&>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
|
||||
expected(const unexpected<_OtherErr>& __unex)
|
||||
noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __unex.error());
|
||||
}
|
||||
: __union_(std::unexpect, __unex.error()), __has_val_(false) {}
|
||||
|
||||
template <class _OtherErr>
|
||||
requires is_constructible_v<_Err, _OtherErr>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
|
||||
expected(unexpected<_OtherErr>&& __unex)
|
||||
noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::move(__unex.error()));
|
||||
}
|
||||
: __union_(std::unexpect, std::move(__unex.error())), __has_val_(false) {}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t) noexcept : __has_val_(true) {}
|
||||
|
||||
@@ -1063,17 +1030,13 @@ public:
|
||||
requires is_constructible_v<_Err, _Args...>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args)
|
||||
noexcept(is_nothrow_constructible_v<_Err, _Args...>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), std::forward<_Args>(__args)...);
|
||||
}
|
||||
: __union_(std::unexpect, std::forward<_Args>(__args)...), __has_val_(false) {}
|
||||
|
||||
template <class _Up, class... _Args>
|
||||
requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args)
|
||||
noexcept(is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
|
||||
: __has_val_(false) {
|
||||
std::construct_at(std::addressof(__union_.__unex_), __il, std::forward<_Args>(__args)...);
|
||||
}
|
||||
: __union_(std::unexpect, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
|
||||
|
||||
private:
|
||||
template <class _Func>
|
||||
@@ -1507,11 +1470,23 @@ private:
|
||||
union __union_t {
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {}
|
||||
|
||||
template <class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
|
||||
: __unex_(std::forward<_Args>(__args)...) {}
|
||||
|
||||
template <class _Func, class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
|
||||
__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
|
||||
: __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
|
||||
|
||||
template <class _Union>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
|
||||
if (__has_val)
|
||||
std::construct_at(std::addressof(__empty_));
|
||||
else
|
||||
std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
|
||||
}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
|
||||
requires(is_trivially_destructible_v<_ErrorType>)
|
||||
= default;
|
||||
@@ -1534,11 +1509,23 @@ private:
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = default;
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
|
||||
|
||||
template <class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
|
||||
: __unex_(std::forward<_Args>(__args)...) {}
|
||||
|
||||
template <class _Func, class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
|
||||
__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
|
||||
: __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
|
||||
|
||||
template <class _Union>
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
|
||||
if (__has_val)
|
||||
std::construct_at(std::addressof(__empty_));
|
||||
else
|
||||
std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_);
|
||||
}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
|
||||
requires(is_trivially_destructible_v<_ErrorType>)
|
||||
= default;
|
||||
|
||||
@@ -81,6 +81,14 @@ constexpr bool test() {
|
||||
assert(e.value().i == 10);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<TailClobberer<0>, bool> e(std::unexpect);
|
||||
auto list = {4, 5, 6};
|
||||
e.emplace(list);
|
||||
assert(e.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,13 @@ constexpr bool test() {
|
||||
assert(e.value() == 10);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<TailClobberer<0>, bool> e(std::unexpect);
|
||||
e.emplace();
|
||||
assert(e.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
template <class T1, class Err1, class T2, class Err2>
|
||||
@@ -161,13 +162,19 @@ constexpr bool test() {
|
||||
assert(e1.error() == 5);
|
||||
}
|
||||
|
||||
// convert TailClobberer
|
||||
{
|
||||
const std::expected<TailClobbererNonTrivialMove<0>, char> e1;
|
||||
std::expected<TailClobberer<0>, char> e2 = e1;
|
||||
assert(e2.has_value());
|
||||
assert(e1.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct ThrowingInt {
|
||||
ThrowingInt(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
template <class T1, class Err1, class T2, class Err2>
|
||||
@@ -160,13 +161,19 @@ constexpr bool test() {
|
||||
assert(e1.error().get() == 0);
|
||||
}
|
||||
|
||||
// convert TailClobberer
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0>, char> e1;
|
||||
std::expected<TailClobberer<0>, char> e2 = std::move(e1);
|
||||
assert(e2.has_value());
|
||||
assert(e1.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct ThrowingInt {
|
||||
ThrowingInt(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
struct NonCopyable {
|
||||
NonCopyable(const NonCopyable&) = delete;
|
||||
@@ -93,13 +94,26 @@ constexpr bool test() {
|
||||
assert(!e2.has_value());
|
||||
assert(e2.error() == 5);
|
||||
}
|
||||
|
||||
// copy TailClobberer as value
|
||||
{
|
||||
const std::expected<TailClobberer<0>, bool> e1;
|
||||
auto e2 = e1;
|
||||
assert(e2.has_value());
|
||||
}
|
||||
|
||||
// copy TailClobberer as error
|
||||
{
|
||||
const std::expected<bool, TailClobberer<1>> e1(std::unexpect);
|
||||
auto e2 = e1;
|
||||
assert(!e2.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing() = default;
|
||||
Throwing(const Throwing&) { throw Except{}; }
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
struct NoDedefaultCtor {
|
||||
NoDedefaultCtor() = delete;
|
||||
@@ -45,6 +46,7 @@ constexpr void testDefaultCtor() {
|
||||
|
||||
template <class T>
|
||||
constexpr void testTypes() {
|
||||
testDefaultCtor<T, bool>();
|
||||
testDefaultCtor<T, int>();
|
||||
testDefaultCtor<T, NoDedefaultCtor>();
|
||||
}
|
||||
@@ -52,13 +54,12 @@ constexpr void testTypes() {
|
||||
constexpr bool test() {
|
||||
testTypes<int>();
|
||||
testTypes<MyInt>();
|
||||
testTypes<TailClobberer<0>>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing() { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
static_assert(std::is_constructible_v<std::expected<int, int>, std::in_place_t>);
|
||||
@@ -54,24 +55,24 @@ struct CopyOnly {
|
||||
friend constexpr bool operator==(const CopyOnly& mi, int ii) { return mi.i == ii; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template <class T, class E = int>
|
||||
constexpr void testInt() {
|
||||
std::expected<T, int> e(std::in_place, 5);
|
||||
std::expected<T, E> e(std::in_place, 5);
|
||||
assert(e.has_value());
|
||||
assert(e.value() == 5);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <class T, class E = int>
|
||||
constexpr void testLValue() {
|
||||
T t(5);
|
||||
std::expected<T, int> e(std::in_place, t);
|
||||
std::expected<T, E> e(std::in_place, t);
|
||||
assert(e.has_value());
|
||||
assert(e.value() == 5);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <class T, class E = int>
|
||||
constexpr void testRValue() {
|
||||
std::expected<T, int> e(std::in_place, T(5));
|
||||
std::expected<T, E> e(std::in_place, T(5));
|
||||
assert(e.has_value());
|
||||
assert(e.value() == 5);
|
||||
}
|
||||
@@ -80,10 +81,13 @@ constexpr bool test() {
|
||||
testInt<int>();
|
||||
testInt<CopyOnly>();
|
||||
testInt<MoveOnly>();
|
||||
testInt<TailClobberer<0>, bool>();
|
||||
testLValue<int>();
|
||||
testLValue<CopyOnly>();
|
||||
testLValue<TailClobberer<0>, bool>();
|
||||
testRValue<int>();
|
||||
testRValue<MoveOnly>();
|
||||
testRValue<TailClobberer<0>, bool>();
|
||||
|
||||
// no arg
|
||||
{
|
||||
@@ -111,8 +115,6 @@ constexpr bool test() {
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
static_assert(
|
||||
@@ -90,13 +91,17 @@ constexpr bool test() {
|
||||
assert(m.get() == 0);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<TailClobberer<0>, bool> e(std::in_place, {1, 2, 3});
|
||||
assert(e.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(std::initializer_list<int>, int) { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
struct NonMovable {
|
||||
NonMovable(NonMovable&&) = delete;
|
||||
@@ -112,13 +113,28 @@ constexpr bool test() {
|
||||
assert(e2.error() == 5);
|
||||
assert(!e1.has_value());
|
||||
}
|
||||
|
||||
// move TailClobbererNonTrivialMove as value
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0>, bool> e1;
|
||||
auto e2 = std::move(e1);
|
||||
assert(e2.has_value());
|
||||
assert(e1.has_value());
|
||||
}
|
||||
|
||||
// move TailClobbererNonTrivialMove as error
|
||||
{
|
||||
std::expected<bool, TailClobbererNonTrivialMove<1>> e1(std::unexpect);
|
||||
auto e2 = std::move(e1);
|
||||
assert(!e2.has_value());
|
||||
assert(!e1.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing() = default;
|
||||
Throwing(Throwing&&) { throw Except{}; }
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
static_assert(std::is_constructible_v<std::expected<int, int>, int>);
|
||||
@@ -70,24 +71,24 @@ struct CopyOnly {
|
||||
struct BaseError {};
|
||||
struct DerivedError : BaseError {};
|
||||
|
||||
template <class T>
|
||||
template <class T, class E = int>
|
||||
constexpr void testInt() {
|
||||
std::expected<T, int> e(5);
|
||||
std::expected<T, E> e(5);
|
||||
assert(e.has_value());
|
||||
assert(e.value() == 5);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <class T, class E = int>
|
||||
constexpr void testLValue() {
|
||||
T t(5);
|
||||
std::expected<T, int> e(t);
|
||||
std::expected<T, E> e(t);
|
||||
assert(e.has_value());
|
||||
assert(e.value() == 5);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <class T, class E = int>
|
||||
constexpr void testRValue() {
|
||||
std::expected<T, int> e(T(5));
|
||||
std::expected<T, E> e(T(5));
|
||||
assert(e.has_value());
|
||||
assert(e.value() == 5);
|
||||
}
|
||||
@@ -96,10 +97,13 @@ constexpr bool test() {
|
||||
testInt<int>();
|
||||
testInt<CopyOnly>();
|
||||
testInt<MoveOnly>();
|
||||
testInt<TailClobberer<0>, bool>();
|
||||
testLValue<int>();
|
||||
testLValue<CopyOnly>();
|
||||
testLValue<TailClobberer<0>, bool>();
|
||||
testRValue<int>();
|
||||
testRValue<MoveOnly>();
|
||||
testRValue<TailClobberer<0>, bool>();
|
||||
|
||||
// Test default template argument.
|
||||
// Without it, the template parameter cannot be deduced from an initializer list
|
||||
@@ -153,8 +157,6 @@ constexpr bool test() {
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
static_assert(std::is_constructible_v<std::expected<int, int>, std::unexpect_t>);
|
||||
@@ -54,24 +55,24 @@ struct CopyOnly {
|
||||
friend constexpr bool operator==(const CopyOnly& mi, int ii) { return mi.i == ii; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template <class T, class V = int>
|
||||
constexpr void testInt() {
|
||||
std::expected<int, T> e(std::unexpect, 5);
|
||||
std::expected<V, T> e(std::unexpect, 5);
|
||||
assert(!e.has_value());
|
||||
assert(e.error() == 5);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <class T, class V = int>
|
||||
constexpr void testLValue() {
|
||||
T t(5);
|
||||
std::expected<int, T> e(std::unexpect, t);
|
||||
std::expected<V, T> e(std::unexpect, t);
|
||||
assert(!e.has_value());
|
||||
assert(e.error() == 5);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <class T, class V = int>
|
||||
constexpr void testRValue() {
|
||||
std::expected<int, T> e(std::unexpect, T(5));
|
||||
std::expected<V, T> e(std::unexpect, T(5));
|
||||
assert(!e.has_value());
|
||||
assert(e.error() == 5);
|
||||
}
|
||||
@@ -80,10 +81,13 @@ constexpr bool test() {
|
||||
testInt<int>();
|
||||
testInt<CopyOnly>();
|
||||
testInt<MoveOnly>();
|
||||
testInt<TailClobberer<1>, bool>();
|
||||
testLValue<int>();
|
||||
testLValue<CopyOnly>();
|
||||
testLValue<TailClobberer<1>, bool>();
|
||||
testRValue<int>();
|
||||
testRValue<MoveOnly>();
|
||||
testRValue<TailClobberer<1>, bool>();
|
||||
|
||||
// no arg
|
||||
{
|
||||
@@ -111,8 +115,6 @@ constexpr bool test() {
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
static_assert(
|
||||
@@ -90,13 +91,17 @@ constexpr bool test() {
|
||||
assert(m.get() == 0);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<bool, TailClobberer<1>> e(std::unexpect, {1, 2, 3});
|
||||
assert(!e.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(std::initializer_list<int>, int) { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints
|
||||
static_assert(std::is_constructible_v<std::expected<int, int>, const std::unexpected<int>&>);
|
||||
@@ -49,10 +50,10 @@ struct MyInt {
|
||||
friend constexpr bool operator==(const MyInt&, const MyInt&) = default;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template <class T, class V = int>
|
||||
constexpr void testUnexpected() {
|
||||
const std::unexpected<int> u(5);
|
||||
std::expected<int, T> e(u);
|
||||
std::expected<V, T> e(u);
|
||||
assert(!e.has_value());
|
||||
assert(e.error() == 5);
|
||||
}
|
||||
@@ -60,13 +61,12 @@ constexpr void testUnexpected() {
|
||||
constexpr bool test() {
|
||||
testUnexpected<int>();
|
||||
testUnexpected<MyInt>();
|
||||
testUnexpected<TailClobberer<1>, bool>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints
|
||||
static_assert(std::is_constructible_v<std::expected<int, int>, std::unexpected<int>>);
|
||||
@@ -49,10 +50,10 @@ struct MyInt {
|
||||
friend constexpr bool operator==(const MyInt&, const MyInt&) = default;
|
||||
};
|
||||
|
||||
template <class Err>
|
||||
template <class Err, class V = int>
|
||||
constexpr void testInt() {
|
||||
std::unexpected<int> u(5);
|
||||
std::expected<int, Err> e(std::move(u));
|
||||
std::expected<V, Err> e(std::move(u));
|
||||
assert(!e.has_value());
|
||||
assert(e.error() == 5);
|
||||
}
|
||||
@@ -69,14 +70,13 @@ constexpr bool test() {
|
||||
testInt<int>();
|
||||
testInt<MyInt>();
|
||||
testInt<MoveOnly>();
|
||||
testInt<TailClobberer<1>, bool>();
|
||||
testMoveOnly();
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
#include <cassert>
|
||||
#include <concepts>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test noexcept
|
||||
template <class T>
|
||||
@@ -43,6 +45,36 @@ constexpr bool test() {
|
||||
assert(!e.has_value());
|
||||
}
|
||||
|
||||
// The following tests check that the "has_value" flag is not overwritten
|
||||
// by the constructor of the value. This could happen because the flag is
|
||||
// stored in the tail padding of the value.
|
||||
//
|
||||
// The first test is a simplified version of the real code where this was
|
||||
// first observed.
|
||||
//
|
||||
// The other tests use a synthetic struct that clobbers its tail padding
|
||||
// on construction, making the issue easier to reproduce.
|
||||
//
|
||||
// See https://github.com/llvm/llvm-project/issues/68552 and the linked PR.
|
||||
{
|
||||
auto f1 = [] -> std::expected<std::optional<int>, long> { return 0; };
|
||||
|
||||
auto f2 = [&f1] -> std::expected<std::optional<int>, int> {
|
||||
return f1().transform_error([](auto) { return 0; });
|
||||
};
|
||||
|
||||
auto e = f2();
|
||||
assert(e.has_value());
|
||||
}
|
||||
{
|
||||
const std::expected<TailClobberer<0>, bool> e = {};
|
||||
// clang-cl does not support [[no_unique_address]] yet.
|
||||
#if !(defined(TEST_COMPILER_CLANG) && defined(_MSC_VER))
|
||||
LIBCPP_STATIC_ASSERT(sizeof(TailClobberer<0>) == sizeof(e));
|
||||
#endif
|
||||
assert(e.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ constexpr bool test() {
|
||||
std::expected<TrackedMove<true>, TrackedMove<false>> e1(std::in_place, 5);
|
||||
std::expected<TrackedMove<true>, TrackedMove<false>> e2(std::unexpect, 10);
|
||||
|
||||
e1.swap(e2);
|
||||
swap(e1, e2);
|
||||
|
||||
assert(!e1.has_value());
|
||||
assert(e1.error().i == 10);
|
||||
@@ -180,6 +180,35 @@ constexpr bool test() {
|
||||
assert(!e2.error().swapCalled);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
// is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> y(std::unexpect);
|
||||
|
||||
swap(x, y);
|
||||
|
||||
// Both of these would fail if adjusting the "has value" flags happened
|
||||
// _before_ constructing the member objects inside the `swap`.
|
||||
assert(!x.has_value());
|
||||
assert(y.has_value());
|
||||
}
|
||||
|
||||
// !is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> y(std::unexpect);
|
||||
|
||||
swap(x, y);
|
||||
|
||||
// Both of these would fail if adjusting the "has value" flags happened
|
||||
// _before_ constructing the member objects inside the `swap`.
|
||||
assert(!x.has_value());
|
||||
assert(y.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -210,6 +239,39 @@ void testException() {
|
||||
assert(*e1 == 5);
|
||||
}
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
// is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> y(std::unexpect);
|
||||
try {
|
||||
swap(x, y);
|
||||
assert(false);
|
||||
} catch (Except) {
|
||||
assert(x.has_value());
|
||||
// This would fail if `TailClobbererNonTrivialMove<1>` clobbered the
|
||||
// flag when rolling back the swap.
|
||||
assert(!y.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
// !is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect);
|
||||
try {
|
||||
swap(x, y);
|
||||
assert(false);
|
||||
} catch (Except) {
|
||||
// This would fail if `TailClobbererNonTrivialMove<0>` clobbered the
|
||||
// flag when rolling back the swap.
|
||||
assert(x.has_value());
|
||||
assert(!y.has_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // TEST_HAS_NO_EXCEPTIONS
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ static_assert(!HasMemberSwap<MoveMayThrow, MoveMayThrow>);
|
||||
|
||||
// Test noexcept
|
||||
template <class T, class E>
|
||||
concept MemberSwapNoexcept =
|
||||
concept MemberSwapNoexcept = //
|
||||
requires(std::expected<T, E> x, std::expected<T, E> y) {
|
||||
{ x.swap(y) } noexcept;
|
||||
};
|
||||
@@ -198,6 +198,35 @@ constexpr bool test() {
|
||||
assert(!e2.error().swapCalled);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
// is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> y(std::unexpect);
|
||||
|
||||
x.swap(y);
|
||||
|
||||
// Both of these would fail if adjusting the "has value" flags happened
|
||||
// _before_ constructing the member objects inside the `swap`.
|
||||
assert(!x.has_value());
|
||||
assert(y.has_value());
|
||||
}
|
||||
|
||||
// !is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> y(std::unexpect);
|
||||
|
||||
x.swap(y);
|
||||
|
||||
// Both of these would fail if adjusting the "has value" flags happened
|
||||
// _before_ constructing the member objects inside the `swap`.
|
||||
assert(!x.has_value());
|
||||
assert(y.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,6 +257,39 @@ void testException() {
|
||||
assert(*e1 == 5);
|
||||
}
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
// is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> y(std::unexpect);
|
||||
try {
|
||||
x.swap(y);
|
||||
assert(false);
|
||||
} catch (Except) {
|
||||
assert(x.has_value());
|
||||
// This would fail if `TailClobbererNonTrivialMove<1>` clobbered the
|
||||
// flag when rolling back the swap.
|
||||
assert(!y.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
// !is_nothrow_move_constructible_v<E>
|
||||
{
|
||||
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place);
|
||||
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect);
|
||||
try {
|
||||
x.swap(y);
|
||||
assert(false);
|
||||
} catch (Except) {
|
||||
// This would fail if `TailClobbererNonTrivialMove<0>` clobbered the
|
||||
// flag when rolling back the swap.
|
||||
assert(x.has_value());
|
||||
assert(!y.has_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // TEST_HAS_NO_EXCEPTIONS
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
template <class T1, class Err1, class T2, class Err2>
|
||||
@@ -97,13 +98,19 @@ constexpr bool test() {
|
||||
assert(e1.error() == 5);
|
||||
}
|
||||
|
||||
// convert TailClobberer
|
||||
{
|
||||
const std::expected<void, TailClobbererNonTrivialMove<1>> e1(std::unexpect);
|
||||
std::expected<void, TailClobberer<1>> e2 = e1;
|
||||
assert(!e2.has_value());
|
||||
assert(!e1.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct ThrowingInt {
|
||||
ThrowingInt(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
template <class T1, class Err1, class T2, class Err2>
|
||||
@@ -98,13 +99,19 @@ constexpr bool test() {
|
||||
assert(e1.error().get() == 0);
|
||||
}
|
||||
|
||||
// convert TailClobberer
|
||||
{
|
||||
std::expected<void, TailClobbererNonTrivialMove<1>> e1(std::unexpect);
|
||||
std::expected<void, TailClobberer<1>> e2 = std::move(e1);
|
||||
assert(!e2.has_value());
|
||||
assert(!e1.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct ThrowingInt {
|
||||
ThrowingInt(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
struct NonCopyable {
|
||||
NonCopyable(const NonCopyable&) = delete;
|
||||
@@ -62,13 +63,19 @@ constexpr bool test() {
|
||||
assert(!e2.has_value());
|
||||
assert(e2.error() == 5);
|
||||
}
|
||||
|
||||
// copy TailClobberer as error
|
||||
{
|
||||
const std::expected<void, TailClobberer<1>> e1(std::unexpect);
|
||||
auto e2 = e1;
|
||||
assert(!e2.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing() = default;
|
||||
Throwing(const Throwing&) { throw Except{}; }
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
struct NonMovable {
|
||||
NonMovable(NonMovable&&) = delete;
|
||||
@@ -76,13 +77,20 @@ constexpr bool test() {
|
||||
assert(e2.error() == 5);
|
||||
assert(!e1.has_value());
|
||||
}
|
||||
|
||||
// move TailClobbererNonTrivialMove as error
|
||||
{
|
||||
std::expected<void, TailClobbererNonTrivialMove<1>> e1(std::unexpect);
|
||||
auto e2 = std::move(e1);
|
||||
assert(!e2.has_value());
|
||||
assert(!e1.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing() = default;
|
||||
Throwing(Throwing&&) { throw Except{}; }
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
static_assert(std::is_constructible_v<std::expected<void, int>, std::unexpect_t>);
|
||||
@@ -80,10 +81,13 @@ constexpr bool test() {
|
||||
testInt<int>();
|
||||
testInt<CopyOnly>();
|
||||
testInt<MoveOnly>();
|
||||
testInt<TailClobberer<1>>();
|
||||
testLValue<int>();
|
||||
testLValue<CopyOnly>();
|
||||
testLValue<TailClobberer<1>>();
|
||||
testRValue<int>();
|
||||
testRValue<MoveOnly>();
|
||||
testRValue<TailClobberer<1>>();
|
||||
|
||||
// no arg
|
||||
{
|
||||
@@ -111,8 +115,6 @@ constexpr bool test() {
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints:
|
||||
static_assert(
|
||||
@@ -89,13 +90,17 @@ constexpr bool test() {
|
||||
assert(m.get() == 0);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<void, TailClobberer<1>> e(std::unexpect, {1, 2, 3});
|
||||
assert(!e.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(std::initializer_list<int>, int) { throw Except{}; };
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints
|
||||
static_assert(std::is_constructible_v<std::expected<void, int>, const std::unexpected<int>&>);
|
||||
@@ -60,13 +61,12 @@ constexpr void testUnexpected() {
|
||||
constexpr bool test() {
|
||||
testUnexpected<int>();
|
||||
testUnexpected<MyInt>();
|
||||
testUnexpected<TailClobberer<1>>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "MoveOnly.h"
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test Constraints
|
||||
static_assert(std::is_constructible_v<std::expected<void, int>, std::unexpected<int>>);
|
||||
@@ -69,14 +70,13 @@ constexpr bool test() {
|
||||
testInt<int>();
|
||||
testInt<MyInt>();
|
||||
testInt<MoveOnly>();
|
||||
testInt<TailClobberer<1>>();
|
||||
testMoveOnly();
|
||||
return true;
|
||||
}
|
||||
|
||||
void testException() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
struct Except {};
|
||||
|
||||
struct Throwing {
|
||||
Throwing(int) { throw Except{}; }
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "../../types.h"
|
||||
|
||||
// Test noexcept
|
||||
template <class T>
|
||||
@@ -43,6 +44,17 @@ constexpr bool test() {
|
||||
assert(!e.has_value());
|
||||
}
|
||||
|
||||
// See comments of the corresponding test in
|
||||
// "expected.expected/observers/has_value.pass.cpp".
|
||||
{
|
||||
const std::expected<void, TailClobberer<1>> e(std::unexpect);
|
||||
// clang-cl does not support [[no_unique_address]] yet.
|
||||
#if !(defined(TEST_COMPILER_CLANG) && defined(_MSC_VER))
|
||||
LIBCPP_STATIC_ASSERT(sizeof(TailClobberer<1>) == sizeof(e));
|
||||
#endif
|
||||
assert(!e.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ constexpr bool test() {
|
||||
std::expected<void, Traced> e1(std::in_place);
|
||||
std::expected<void, Traced> e2(std::unexpect, s, 10);
|
||||
|
||||
e1.swap(e2);
|
||||
swap(e1, e2);
|
||||
|
||||
assert(!e1.has_value());
|
||||
assert(e1.error().data_ == 10);
|
||||
@@ -107,7 +107,7 @@ constexpr bool test() {
|
||||
std::expected<void, Traced> e1(std::unexpect, s, 10);
|
||||
std::expected<void, Traced> e2(std::in_place);
|
||||
|
||||
e1.swap(e2);
|
||||
swap(e1, e2);
|
||||
|
||||
assert(e1.has_value());
|
||||
assert(!e2.has_value());
|
||||
@@ -117,6 +117,19 @@ constexpr bool test() {
|
||||
assert(s.dtorCalled);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<void, TailClobbererNonTrivialMove<1>> x(std::in_place);
|
||||
std::expected<void, TailClobbererNonTrivialMove<1>> y(std::unexpect);
|
||||
|
||||
swap(x, y);
|
||||
|
||||
// The next line would fail if adjusting the "has value" flag happened
|
||||
// _before_ constructing the member object inside the `swap`.
|
||||
assert(!x.has_value());
|
||||
assert(y.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -151,6 +164,21 @@ void testException() {
|
||||
assert(!e2Destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> x(std::in_place);
|
||||
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> y(std::unexpect);
|
||||
try {
|
||||
swap(x, y);
|
||||
assert(false);
|
||||
} catch (Except) {
|
||||
// This would fail if `TailClobbererNonTrivialMove<0, false, true>`
|
||||
// clobbered the flag before throwing the exception.
|
||||
assert(x.has_value());
|
||||
assert(!y.has_value());
|
||||
}
|
||||
}
|
||||
#endif // TEST_HAS_NO_EXCEPTIONS
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ struct MoveMayThrow {
|
||||
};
|
||||
|
||||
template <class E>
|
||||
concept MemberSwapNoexcept =
|
||||
concept MemberSwapNoexcept = //
|
||||
requires(std::expected<void, E> x, std::expected<void, E> y) {
|
||||
{ x.swap(y) } noexcept;
|
||||
};
|
||||
@@ -126,6 +126,19 @@ constexpr bool test() {
|
||||
assert(s.dtorCalled);
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<void, TailClobbererNonTrivialMove<1>> x(std::in_place);
|
||||
std::expected<void, TailClobbererNonTrivialMove<1>> y(std::unexpect);
|
||||
|
||||
x.swap(y);
|
||||
|
||||
// The next line would fail if adjusting the "has value" flag happened
|
||||
// _before_ constructing the member object inside the `swap`.
|
||||
assert(!x.has_value());
|
||||
assert(y.has_value());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -160,6 +173,21 @@ void testException() {
|
||||
assert(!e2Destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
// TailClobberer
|
||||
{
|
||||
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> x(std::in_place);
|
||||
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> y(std::unexpect);
|
||||
try {
|
||||
x.swap(y);
|
||||
assert(false);
|
||||
} catch (Except) {
|
||||
// This would fail if `TailClobbererNonTrivialMove<0, false, true>`
|
||||
// clobbered the flag before throwing the exception.
|
||||
assert(x.has_value());
|
||||
assert(!y.has_value());
|
||||
}
|
||||
}
|
||||
#endif // TEST_HAS_NO_EXCEPTIONS
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#ifndef TEST_STD_UTILITIES_EXPECTED_TYPES_H
|
||||
#define TEST_STD_UTILITIES_EXPECTED_TYPES_H
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "test_macros.h"
|
||||
|
||||
template <bool copyMoveNoexcept, bool convertNoexcept = true>
|
||||
@@ -150,4 +152,51 @@ struct MoveOnlyErrorType {
|
||||
MoveOnlyErrorType& operator=(const MoveOnlyErrorType&) = delete;
|
||||
};
|
||||
|
||||
// This type has one byte of tail padding where `std::expected` may put its
|
||||
// "has value" flag. The constructor will clobber all bytes including the
|
||||
// tail padding. With this type we can check that `std::expected` handles
|
||||
// the case where the "has value" flag is an overlapping subobject correctly.
|
||||
//
|
||||
// See https://github.com/llvm/llvm-project/issues/68552 for details.
|
||||
template <int Constant>
|
||||
struct TailClobberer {
|
||||
constexpr TailClobberer() noexcept {
|
||||
if (!std::is_constant_evaluated()) {
|
||||
std::memset(this, Constant, sizeof(*this));
|
||||
}
|
||||
// Always set `b` itself to `false` so that the comparison works.
|
||||
b = false;
|
||||
}
|
||||
constexpr TailClobberer(const TailClobberer&) : TailClobberer() {}
|
||||
constexpr TailClobberer(TailClobberer&&) = default;
|
||||
// Converts from `int`/`std::initializer_list<int>, used in some tests.
|
||||
constexpr TailClobberer(int) : TailClobberer() {}
|
||||
constexpr TailClobberer(std::initializer_list<int>) noexcept : TailClobberer() {}
|
||||
|
||||
friend constexpr bool operator==(const TailClobberer&, const TailClobberer&) = default;
|
||||
|
||||
friend constexpr void swap(TailClobberer&, TailClobberer&){};
|
||||
|
||||
private:
|
||||
alignas(2) bool b;
|
||||
};
|
||||
static_assert(!std::is_trivially_copy_constructible_v<TailClobberer<0>>);
|
||||
static_assert(std::is_trivially_move_constructible_v<TailClobberer<0>>);
|
||||
|
||||
template <int Constant, bool Noexcept = true, bool ThrowOnMove = false>
|
||||
struct TailClobbererNonTrivialMove : TailClobberer<Constant> {
|
||||
using TailClobberer<Constant>::TailClobberer;
|
||||
constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept(Noexcept) : TailClobberer<Constant>() {
|
||||
#ifndef TEST_HAS_NO_EXCEPTIONS
|
||||
if constexpr (!Noexcept && ThrowOnMove)
|
||||
throw Except{};
|
||||
#endif
|
||||
}
|
||||
};
|
||||
static_assert(!std::is_trivially_copy_constructible_v<TailClobbererNonTrivialMove<0>>);
|
||||
static_assert(std::is_move_constructible_v<TailClobbererNonTrivialMove<0>>);
|
||||
static_assert(!std::is_trivially_move_constructible_v<TailClobbererNonTrivialMove<0>>);
|
||||
static_assert(std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, true>>);
|
||||
static_assert(!std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, false>>);
|
||||
|
||||
#endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H
|
||||
|
||||
Reference in New Issue
Block a user