[libcxx-commits] [libcxx] 18a4c90 - [libc++] Fix bug where `optional<T&>` couldn't be constructed from `transform()` (#203462)
via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Jun 12 18:58:41 PDT 2026
Author: William Tran-Viet
Date: 2026-06-13T09:58:38+08:00
New Revision: 18a4c90c5191cb2bade557061ee4afa548468c1e
URL: https://github.com/llvm/llvm-project/commit/18a4c90c5191cb2bade557061ee4afa548468c1e
DIFF: https://github.com/llvm/llvm-project/commit/18a4c90c5191cb2bade557061ee4afa548468c1e.diff
LOG: [libc++] Fix bug where `optional<T&>` couldn't be constructed from `transform()` (#203462)
- Add the proper from monadic base constructor
- Fix the constraint so it allows references.
- Add tests
Added:
Modified:
libcxx/include/optional
libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp
libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
Removed:
################################################################################
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 3a8191a3d1971..97e8b27e7f2d1 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -571,6 +571,14 @@ struct __optional_storage_base<_Tp, true> {
__convert_init_ref_val(std::forward<_UArg>(__uarg));
}
+# if _LIBCPP_STD_VER >= 23
+ template <class _Fp, class... _Args>
+ constexpr __optional_storage_base(__optional_construct_from_invoke_tag, _Fp&& __f, _Args&&... __args) {
+ __convert_init_ref_val(std::forward<invoke_result_t<_Fp, _Args...>>(
+ std::invoke(std::forward<_Fp>(__f), std::forward<_Args>(__args)...)));
+ }
+# endif
+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void reset() noexcept { __value_ = nullptr; }
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __value_ != nullptr; }
@@ -776,6 +784,14 @@ template <class _Tp, class _Up, class... _Args>
inline constexpr bool __is_constructible_for_optional_initializer_list_v<_Tp&, _Up, _Args...> = false;
# endif
+# if _LIBCPP_STD_VER < 26
+template <class _Tp>
+inline constexpr bool __is_valid_optional_contained_type = is_object_v<_Tp>;
+# else
+template <class _Tp>
+inline constexpr bool __is_valid_optional_contained_type = is_object_v<_Tp> || is_lvalue_reference_v<_Tp>;
+# endif
+
template <class _Tp, class = void>
struct __optional_iterator_base : __optional_move_assign_base<_Tp> {
using __optional_move_assign_base<_Tp>::__optional_move_assign_base;
@@ -1137,7 +1153,8 @@ public:
static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
- static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+ static_assert(
+ __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
if (*this)
return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
return optional<_Up>();
@@ -1149,7 +1166,8 @@ public:
static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
- static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+ static_assert(
+ __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
if (*this)
return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
return optional<_Up>();
@@ -1161,7 +1179,8 @@ public:
static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(std::move(value())) should not be std::nullopt_t");
- static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+ static_assert(
+ __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
if (*this)
return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
return optional<_Up>();
@@ -1173,7 +1192,8 @@ public:
static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(std::move(value())) should not be std::nullopt_t");
- static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+ static_assert(
+ __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
if (*this)
return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
return optional<_Up>();
@@ -1348,7 +1368,8 @@ public:
static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
- static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+ static_assert(
+ __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
if (*this)
return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
index 93bbd74cd8678..e806cd8dd5343 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
@@ -261,8 +261,6 @@ constexpr bool test() {
#if TEST_STD_VER >= 26
constexpr bool test_ref() {
- // Test "overloads", only the const (no ref-qualifier) and_then() should be called
-
{ // &
// Without & qualifier on F's operator()
{
@@ -339,6 +337,41 @@ constexpr bool test_ref() {
assert(std::move(i).and_then(std::move(nl)) == std::nullopt);
}
}
+
+ {
+ int i = 1;
+ int j = 2;
+ {
+ std::optional<int&> o(i);
+ std::same_as<std::optional<int&>> decltype(auto) r = o.and_then([&](auto&& ii) {
+ ii += j;
+ return std::optional<int&>(ii);
+ });
+
+ assert(i == 3);
+ assert(r == 3);
+ }
+ {
+ const std::optional<int&> o(i);
+ std::same_as<std::optional<int&>> decltype(auto) r = o.and_then([&](auto&& ii) {
+ ii += j;
+ return std::optional<int&>(ii);
+ });
+
+ assert(i == 5);
+ assert(r == 5);
+ }
+ {
+ std::optional<int&> o{};
+ std::same_as<std::optional<int>> decltype(auto) r = o.and_then([&](auto&&) { return std::optional(1); });
+ assert(r == std::nullopt);
+ }
+ {
+ const std::optional<int&> o{};
+ std::same_as<std::optional<int>> decltype(auto) r = o.and_then([&](auto&&) { return std::optional(1); });
+ assert(r == std::nullopt);
+ }
+ }
return true;
}
#endif
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp
index 326013cd6557f..3a06ce24bfc99 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp
@@ -15,6 +15,7 @@
#include "MoveOnly.h"
#include <cassert>
+#include <concepts>
#include <optional>
struct NonMovable {
@@ -104,6 +105,14 @@ constexpr bool test() {
}) == i);
}
+ {
+ int j = 2;
+ std::optional<int&> opt;
+ std::same_as<std::optional<int&>> decltype(auto) o = opt.or_else([&] { return std::optional<int&>(j); });
+ assert(o == j);
+ assert(&(*o) == &j);
+ }
+
#endif
return true;
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
index b8536b790f749..96303c5796f6d 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
@@ -204,8 +204,6 @@ constexpr bool test() {
#if TEST_STD_VER >= 26
constexpr bool test_ref() {
- // Test that no matter the ref qualifier on the object .transform() is invoked on, only the added
- // const (no ref-qualifier) overload is used
{
std::optional<int&> opt1;
std::same_as<std::optional<int>> decltype(auto) opt1r = opt1.transform([](int i) { return i + 2; });
@@ -229,7 +227,6 @@ constexpr bool test_ref() {
assert(*o2 == 4.0f);
}
- // &
{
// Without & qualifier on F's operator()
{
@@ -250,7 +247,6 @@ constexpr bool test_ref() {
assert(*o3 == 1);
}
}
- // const& overload
{
// Without & qualifier on F's operator()
{
@@ -308,6 +304,23 @@ constexpr bool test_ref() {
auto o6r = o6.transform([](int) { return 42; });
assert(!o6r);
}
+
+ {
+ int i = 42;
+ int j{43};
+
+ auto func = [&j](int&) -> int& { return j; };
+
+ std::optional<int&> opt{i};
+ std::same_as<std::optional<int&>> decltype(auto) o = opt.transform(func);
+ assert(o == j);
+ assert(&(*o) == &j);
+
+ std::same_as<std::optional<int&>> decltype(auto) o2 = std::as_const(opt).transform(func);
+ assert(o2 == j);
+ assert(&(*o2) == &j);
+ }
+
return true;
}
#endif
More information about the libcxx-commits
mailing list