The following compiles on trunk: struct NonMovable { NonMovable() = default; NonMovable(NonMovable&&) = delete; }; static_assert( __reference_converts_from_temporary(int&&, int)); static_assert(!__reference_converts_from_temporary(NonMovable&&, NonMovable)); static_assert( __reference_constructs_from_temporary(int&&, int)); static_assert(!__reference_constructs_from_temporary(NonMovable&&, NonMovable)); When the NonMovable assertions should fail because there *is* a temporary being bound to a reference (in exactly the same way as the int case). We can observe it instantiating constructors that shouldn't be too: template<typename T> struct S { template<typename U = T> S(const S&&) noexcept(U()) {} }; static_assert(__reference_converts_from_temporary(const S<bool>&&, const S<bool>)); static_assert(__reference_converts_from_temporary(const S<void*>&&, const S<void*>)); Which complains about a conversion from void* to bool even though constructors shouldn't be looked at.
This seems to be https://cplusplus.github.io/LWG/issue3819
The issue was already resolved and voted into standard.
I've taken a look and may have a WIP patch. I *think* the problem is that we aren't implementing this bullet properly: Otherwise, VAL<T> is a prvalue that initially has type T. because ref_xes_from_temporary uses build_trait_object + force_rvalue/rvalue, but that actually gets us an xvalue. Every xvalue is an rvalue so the functions work correctly, but here we want a prvalue so that copy elision works. We currently do not seem to have a helper function to get a prvalue given a type T.
The trunk branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>: https://gcc.gnu.org/g:83a903f339af9a1ecde675261191cc69e7b1b721 commit r17-2665-g83a903f339af9a1ecde675261191cc69e7b1b721 Author: Marek Polacek <polacek@redhat.com> Date: Tue Jul 21 12:59:40 2026 -0400 c++: implement LWG 3819, reference_xes_from_temporary [PR112908] This is an attempt to implement <https://cplusplus.github.io/LWG/issue3819>. My understanding of this issue is that previously, ref_xes_from_temporary was defined by using is_constructible, which is implemented by seeing if T t(declval<Args>()...); is well-formed. But declval always yields an xvalue, never a prvalue. In practice this means that for struct U { U(); U(U&&) = delete; }; struct T { T(U); }; reference_constructs_from_temporary_v<const T&, U> is false due to the deleted move ctor. But if we have a prvalue, then the call to the move ctor should be elided and so it doesn't matter that it's deleted. So the result should be 'true'. Our ref_xes_from_temporary already doesn't check is_constructible<T, U> as the comment says, but we always use build_trait_object which gives us an xvalue. What we need is to implement [meta.unary.prop]/5.2: Otherwise [not a reference or function type], VAL<T> is a prvalue that initially has type T. For this I've added build_prvalue_trait_object. The finish_trait_expr change is so that get_target_expr doesn't crash on an incomplete type. This change should be correct since https://cplusplus.github.io/LWG/issue2939 didn't adjust reference_xes_from_temporary the same way as is_convertible/constructible. PR c++/112908 gcc/cp/ChangeLog: * method.cc (build_prvalue_trait_object): New. (ref_xes_from_temporary): Use build_prvalue_trait_object. Use deferring_access_check_sentinel and cp_unevaluated. Don't call force_rvalue or rvalue. * semantics.cc (finish_trait_expr) <case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY>: Actually check completeness. gcc/testsuite/ChangeLog: * g++.dg/ext/is_constructible8.C: Move __reference_*_from_temporary testing to a new test. * g++.dg/ext/reference_xes_from_temporary2.C: New test. * g++.dg/ext/reference_xes_from_temporary3.C: New test. Reviewed-by: Jason Merrill <jason@redhat.com>
Should be fixed.