William Tran-Viet 9f5efd5d03
[libc++] Correct optional<T&> implementation (#174537)
Resolves #174350

- Several issues were found in the current implementation of
`optional<T&>`
- `value()`, `operator*()`, `and_then()`, `transform()`, `operator->()`
still provided their ref-qualified versions for rvalues and `const&`.
- Using the listed methods on an rvalue `optional<T&>` would cause a
compile failure due to a mismatch in return types.
- On the latter, `operator*`, `operator->` would return `const` for a
`optional<T&>`, which is an incorrect deep const.
- A few constructors were missing (`optional<U>&`), and most
constructors relevant to `optional<T&>` were missing `noexcept`
- Constructors and `emplace` were not correctly constructing a `T&` as
specified in _`convert-ref-init-val`_
- Also corrects the behavior of `value_or` which should return
`remove_cv_t<T>` (in our case `decay_t<_Tp>`)
- Add several test cases to verify behavior, update `value_or` tests
2026-01-09 16:33:10 +08:00

223 lines
5.3 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// UNSUPPORTED: c++03, c++11, c++14
// <optional>
// template <class U>
// constexpr EXPLICIT optional(U&& u);
#include <cassert>
#include <optional>
#include <type_traits>
#include "test_macros.h"
#include "archetypes.h"
#include "test_convertible.h"
#include "../optional_helper_types.h"
using std::optional;
struct ImplicitThrow {
constexpr ImplicitThrow(int x) {
if (x != -1)
TEST_THROW(6);
}
};
struct ExplicitThrow {
constexpr explicit ExplicitThrow(int x) {
if (x != -1)
TEST_THROW(6);
}
};
struct ImplicitAny {
template <class U>
constexpr ImplicitAny(U&&) {}
};
template <class To, class From>
constexpr bool implicit_conversion(optional<To>&& opt, const From& v) {
using O = optional<To>;
static_assert(test_convertible<O, From>(), "");
static_assert(!test_convertible<O, void*>(), "");
static_assert(!test_convertible<O, From, int>(), "");
return opt && *opt == static_cast<To>(v);
}
template <class To, class Input, class Expect>
constexpr bool explicit_conversion(Input&& in, const Expect& v) {
using O = optional<To>;
static_assert(std::is_constructible<O, Input>::value, "");
static_assert(!std::is_convertible<Input, O>::value, "");
static_assert(!std::is_constructible<O, void*>::value, "");
static_assert(!std::is_constructible<O, Input, int>::value, "");
optional<To> opt(std::forward<Input>(in));
optional<To> opt2{std::forward<Input>(in)};
return opt && *opt == static_cast<To>(v) && (opt2 && *opt2 == static_cast<To>(v));
}
void test_implicit() {
{
static_assert(implicit_conversion<long long>(42, 42), "");
}
{
static_assert(implicit_conversion<long double>(3.14, 3.14), "");
}
{
int x = 42;
optional<void* const> o(&x);
assert(*o == &x);
}
{
using T = TrivialTestTypes::TestType;
static_assert(implicit_conversion<T>(42, 42), "");
}
{
using T = TestTypes::TestType;
assert(implicit_conversion<T>(3, T(3)));
}
{
using T = TestTypes::TestType;
optional<T> opt({3});
assert(opt && *opt == static_cast<T>(3));
}
{
using O = optional<ImplicitAny>;
static_assert(!test_convertible<O, std::in_place_t>(), "");
static_assert(!test_convertible<O, std::in_place_t&>(), "");
static_assert(!test_convertible<O, const std::in_place_t&>(), "");
static_assert(!test_convertible<O, std::in_place_t&&>(), "");
static_assert(!test_convertible<O, const std::in_place_t&&>(), "");
}
#ifndef TEST_HAS_NO_EXCEPTIONS
{
try {
using T = ImplicitThrow;
optional<T> t = 42;
assert(false);
((void)t);
} catch (int) {
}
}
#endif
}
void test_explicit() {
{
using T = ExplicitTrivialTestTypes::TestType;
static_assert(explicit_conversion<T>(42, 42), "");
}
{
using T = ExplicitConstexprTestTypes::TestType;
static_assert(explicit_conversion<T>(42, 42), "");
static_assert(!std::is_convertible<int, T>::value, "");
}
{
using T = ExplicitTestTypes::TestType;
T::reset();
{
assert(explicit_conversion<T>(42, 42));
assert(T::alive == 0);
}
T::reset();
{
optional<T> t(42);
assert(T::alive == 1);
assert(T::value_constructed == 1);
assert(T::move_constructed == 0);
assert(T::copy_constructed == 0);
assert(t.value().value == 42);
}
assert(T::alive == 0);
}
#ifndef TEST_HAS_NO_EXCEPTIONS
{
try {
using T = ExplicitThrow;
optional<T> t(42);
assert(false);
} catch (int) {
}
}
#endif
}
#if TEST_STD_VER >= 26
constexpr bool test_ref() {
{
int i = 0;
std::optional<int&> o(i);
ASSERT_NOEXCEPT(std::optional<int&>(i));
assert(o.has_value());
assert(&(*o) == &i);
assert(*o == 0);
assert(o.value() == 0);
}
{
ReferenceConversion<int> t{1, 2};
ASSERT_NOEXCEPT(std::optional<int&>(t));
std::optional<int&> o(t);
assert(o.has_value());
assert(&(*o) == &t.lvalue);
assert(*o == 1);
}
{
ReferenceConversion<int> t{1, 2};
ASSERT_NOEXCEPT(std::optional<int&>(std::move(t)));
std::optional<int&> o(std::move(t));
assert(o.has_value());
assert(&(*o) == &t.rvalue);
assert(*o == 2);
}
# ifndef TEST_HAS_NO_EXCEPTIONS
{
ReferenceConversionThrows<int> t{1, 2, false};
ASSERT_NOT_NOEXCEPT(std::optional<int&>(t));
try {
std::optional<int&> o(t);
assert(o.has_value());
assert(&(*o) == &t.lvalue);
assert(*o == 1);
} catch (int) {
assert(false);
}
}
{
ReferenceConversionThrows<int> t{1, 2, false};
ASSERT_NOT_NOEXCEPT(std::optional<int&>(std::move(t)));
try {
std::optional<int&> o(std::move(t));
assert(o.has_value());
assert(&(*o) == &t.rvalue);
assert(*o == 2);
} catch (int) {
assert(false);
}
}
# endif
return true;
}
#endif
int main(int, char**) {
test_implicit();
test_explicit();
#if TEST_STD_VER >= 26
assert(test_ref());
static_assert(test_ref());
#endif
return 0;
}