Skip to content

[libc++] incorrect constructor in std::optional::optional in C++26 #194415

Description

@vogelsgesang

I tried to adopt C++26 for a downstream project while running into the following issue.

Repro / Our use case

We have the following pattern in our code:

  #include <optional>
  #include <string>
  #include <variant>
  struct UnifiedType {
      std::variant<int, std::string> rep_;
      UnifiedType() = default;
      template <typename T>
      UnifiedType(T&& content) : rep_(std::forward<T>(content)) {}
  };
  int main() {
      std::optional<UnifiedType> a;
      std::optional<UnifiedType> b(a);  // ill-formed in C++26 only
  }

This compiles under C++23, but fails to compile under C++26, using libc++ 22.1.2 and clang 22.1.2.

Our own code is not great, since it violates the rule "Item 26: Avoid overloading on universal references" from "Effective Modern C++".
We essentially overloaded the UnifiedType constructor between T&& and const UnifiedType& (the implicit copy constructor).
We solved this in our code by adding requires(!std::is_same_v<std::remove_cvref_t<T>, UnifiedType>).

Independent of our downstream fix, there still seems to be a bug in libc++.

Bug in libc++

This was introduced in libc++ while implementing #155202 and #174537.
It seems the optional<T> primary template  incorrectly enables two cross-template ctors that belong only to the optional<T&> partial specialization.

optional.optional.general declares only

template<class U> optional(const optional<U>&);
template<class U> optional(optional<U>&&);

on the primary template. The optional<U>& and const optional<U>&& only exist for optional.optional.ref.general and must not appear on the primary template.

Looking at the source code

// optional(optional<U>& rhs)
template <class _Up>
requires(!__libcpp_opt_ref_ctor_deleted<_Up>) && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
(!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up&>
_LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up&, _Tp&>)
optional(optional<_Up>& __rhs) noexcept(is_nothrow_constructible_v<_Tp&, _Up&>) {
this->__construct_from(__rhs);
}
, the intent was probably that the !__libcpp_opt_ref_ctor_deleted<_Up> should disable the constructor here.

However, __libcpp_opt_ref_ctor_deleted is defined as

template <class _Up>
constexpr static bool __libcpp_opt_ref_ctor_deleted =
    is_lvalue_reference_v<_Tp> && reference_constructs_from_temporary_v<_Tp, _Up>;

which evaluates to false for any non-reference type. Hence, the constructor is still enabled also for non-reference types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    c++26libc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions