| Summary: | __reference_{converts,constructs}_from_temporary checks for move constructor when binding prvalue to reference of the same type | ||
|---|---|---|---|
| Product: | gcc | Reporter: | Mital Ashok <mital> |
| Component: | c++ | Assignee: | Marek Polacek <mpolacek> |
| Status: | RESOLVED FIXED | ||
| Severity: | normal | CC: | daniel.kruegler, mpolacek, ppalka, tkaminsk, webrown.cpp |
| Priority: | P3 | Keywords: | accepts-invalid, wrong-code |
| Version: | 14.0 | ||
| Target Milestone: | --- | ||
| See Also: | https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104477 | ||
| Host: | Target: | ||
| Build: | Known to work: | ||
| Known to fail: | Last reconfirmed: | 2023-12-20 00:00:00 | |
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. |
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.