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.
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:
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
UnifiedTypeconstructor betweenT&&andconst 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 theoptional<T&>partial specialization.optional.optional.general declares only
on the primary template. The
optional<U>&andconst optional<U>&&only exist for optional.optional.ref.general and must not appear on the primary template.Looking at the source code
llvm-project/libcxx/include/optional
Lines 1047 to 1054 in cf4f678
!__libcpp_opt_ref_ctor_deleted<_Up>should disable the constructor here.However,
__libcpp_opt_ref_ctor_deletedis defined aswhich evaluates to
falsefor any non-reference type. Hence, the constructor is still enabled also for non-reference types.