On Fri, May 1, 2026 at 2:01 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> This implements LWG 4324, "unique_ptr<void>::operator* is not
> SFINAE-friendly", approved in Croydon, 2026.
>
> The noexcept-specifier added to C++23 by LWG 2762 is ill-formed if the
> pointer type cannot be dereferenced, which means that code which was
> checking whether the function exists (e.g. in a SFINAE context) no
> longer works. Such code was always questionable, because the function
> body was ill-formed if the pointer isn't dereferenceable, so the SFINAE
> check was probably giving the wrong answer, but it was possible to ask
> the question. Since LWG 2762 just asking the question can produce an
> error outside the immediate context, so operator* is no longer
> SFINAE-friendly.
>
> LWG 4324 adds a constraint to the function, so that it doesn't
> participate in overload resolution if it would be ill-formed. That's
> easy to implement for C++20 because we can just add a requires-clause.
>
> For C++11/14/17 we can't constrain it easily, so just adjust the
> noexcept-specifier so that it's not ill-formed. This still means you get
> the wrong answer (i.e. it looks like unique_ptr<void>::operator* is
> callable) but there's no error outside the immediate context. This
> restores the original semantics before the LWG 2762 change, for better
> or worse.
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/unique_ptr.h (unique_ptr::_Nothrow_deref): New
> helper for pre-C++20.
> (unique_ptr::operator*): Either constrain or use _Nothrow_deref.
> * testsuite/20_util/unique_ptr/lwg4324.cc: New test.
> ---
>
> Tested x86_64-linux.
>
> v2: Combined _Can_deref and _S_nothrow_deref() into _Nothrow_deref based
> on suggestion from Tomasz. Clarified in commit msg that for pre-C++20
> this restores the pre-LWG2762 semantics, which is that operator* always
> exists, even if calling it would be ill-formed.
>
LGTM.
>
> libstdc++-v3/include/bits/unique_ptr.h | 21 +++++++++++++++++-
> .../testsuite/20_util/unique_ptr/lwg4324.cc | 22 +++++++++++++++++++
> 2 files changed, 42 insertions(+), 1 deletion(-)
> create mode 100644 libstdc++-v3/testsuite/20_util/unique_ptr/lwg4324.cc
>
> diff --git a/libstdc++-v3/include/bits/unique_ptr.h
> b/libstdc++-v3/include/bits/unique_ptr.h
> index 5b7c8938ff74..f4b2e33d13de 100644
> --- a/libstdc++-v3/include/bits/unique_ptr.h
> +++ b/libstdc++-v3/include/bits/unique_ptr.h
> @@ -289,6 +289,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> __not_<is_array<_Up>>
> >;
>
> +#if ! __cpp_concepts
> + template<typename _Ptr, typename = void>
> + struct _Nothrow_deref
> + : false_type { };
> +
> + template<typename _Ptr>
> + struct _Nothrow_deref<_Ptr,
> __void_t<decltype(*std::declval<_Ptr>())>>
> + : __bool_constant<noexcept(*std::declval<_Ptr>())> { };
> +#endif
> +
> public:
> // Constructors.
>
> @@ -442,7 +452,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> /// Dereference the stored pointer.
> _GLIBCXX23_CONSTEXPR
> typename add_lvalue_reference<element_type>::type
> - operator*() const noexcept(noexcept(*std::declval<pointer>()))
> + operator*() const
> + // _GLIBCXX_RESOLVE_LIB_DEFECTS
> + // 2762. unique_ptr operator*() should be noexcept
> + // 4324. unique_ptr<void>::operator* is not SFINAE-friendly
+#if __cpp_concepts
> + noexcept(noexcept(*std::declval<pointer>()))
> + requires requires { *std::declval<pointer>(); }
> +#else
> + noexcept(_Nothrow_deref<pointer>::value)
> +#endif
> {
> #if _GLIBCXX_USE_BUILTIN_TRAIT(__reference_converts_from_temporary)
> // _GLIBCXX_RESOLVE_LIB_DEFECTS
> diff --git a/libstdc++-v3/testsuite/20_util/unique_ptr/lwg4324.cc
> b/libstdc++-v3/testsuite/20_util/unique_ptr/lwg4324.cc
> new file mode 100644
> index 000000000000..13ebfb929fca
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/unique_ptr/lwg4324.cc
> @@ -0,0 +1,22 @@
> +// { dg-do compile { target c++11 } }
> +
> +// LWG 4324. unique_ptr<void>::operator* is not SFINAE-friendly
> +
> +#include <memory>
> +
> +template<class T, bool = true>
> +struct nothrow_dereference
> +: std::false_type { };
> +
> +template<class T>
> +struct nothrow_dereference<T, noexcept(*std::declval<T>())>
> +: std::true_type { };
> +
> +static_assert( nothrow_dereference<std::unique_ptr<int>>::value, "");
> +static_assert( ! nothrow_dereference<std::unique_ptr<void>>::value, "");
> +
> +#if __cplusplus >= 202002L // C++20
> +template<class T> concept dereferenceable = requires(T& t) { *t; };
> +static_assert( dereferenceable<std::unique_ptr<int>>);
> +static_assert(!dereferenceable<std::unique_ptr<void>>);
> +#endif
> --
> 2.54.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260505/c345ffef/attachment.htm>