From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 48) id 4CC0B3857C47; Mon, 17 Aug 2020 12:51:57 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4CC0B3857C47 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1597668717; bh=Blq/jRtP9GRedW44iglYu84As9E7qVri037lFkCEkDA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=lJEj29wzOaXGbEMl75bnnkakoDGf3vcx9obw4g21iyQCRMBD4fKRZjtVOoITsPuva +naQo4yzvFAcNXBmDBqVvtiRAAvshbhnp7rduMwDGnxD8nHcN+OnLl5NNXewwNienw GRLNP8WGH3UREDY9phzBbCPLt02Qxp269gowrC8o= From: "redi at gcc dot gnu.org" To: gcc-bugs@gcc.gnu.org Subject: [Bug c++/94062] Cannot construct tuple from convertible types Date: Mon, 17 Aug 2020 12:51:57 +0000 X-Bugzilla-Reason: CC X-Bugzilla-Type: changed X-Bugzilla-Watch-Reason: None X-Bugzilla-Product: gcc X-Bugzilla-Component: c++ X-Bugzilla-Version: 9.2.1 X-Bugzilla-Keywords: rejects-valid X-Bugzilla-Severity: normal X-Bugzilla-Who: redi at gcc dot gnu.org X-Bugzilla-Status: NEW X-Bugzilla-Resolution: X-Bugzilla-Priority: P3 X-Bugzilla-Assigned-To: unassigned at gcc dot gnu.org X-Bugzilla-Target-Milestone: --- X-Bugzilla-Flags: X-Bugzilla-Changed-Fields: Message-ID: In-Reply-To: References: Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Bugzilla-URL: http://gcc.gnu.org/bugzilla/ Auto-Submitted: auto-generated MIME-Version: 1.0 X-BeenThere: gcc-bugs@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-bugs mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 17 Aug 2020 12:51:57 -0000 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=3D94062 --- Comment #13 from Jonathan Wakely --- (In reply to m.cencora from comment #12) > So unless I am missing something, I see no escape hatch for making such > constructor ill-formed for some types. Consider: #include struct X { template X(T) { static_assert(!std::is_same_v); } }; static_assert(std::is_constructible_v); static_assert(std::is_constructible_v, int>); std::tuple t{1}; The fact that a type meets a function's constraints does not mean it has to compile, it just means the function is not removed from the candidate funct= ions for overload resolution. The standard is pretty clear about that: Constraints: the conditions for the function=E2=80=99s participation in ove= rload resolution That's it. It doesn't mean any more than that. The constructor's effects say it initializes the element from the argument,= and if that initialization is ill-formed (e.g. because it requires a copy that isn't elided) then the constructor is ill-formed. > > That would mean that std::tuple changes layout if you later add a = move > > constructor to Bar. >=20 > Not sure how, because you can create std::tuple already by using oth= er > constructors, e.g.: That's irrelevant. I'm talking about a change that would make it possible to construct non-movable types, so if you change whether a type is movable or = not, it would affect std::tuple. The fact other constructors are already present= is irrelevant, I'm not talking about making anything depend on those. Bar is an empty class, so we will try to make tuple use a potentially-overlapping subobject (storing a Bar as a base class today, or a data member with [[no_unique_address] in the near future). That doesn't sup= port initialization from ConvertibleToBar because the copy cannot be elided. I could change the tuple code to not use a potentially-overlapping subobject for non-movable types, so that copy elision works. But now if you add a move constructor the decision of whether to use a potentially-overlapping subobj= ect changes. So that means the std::tuple layout would depend on the presence o= f a move constructor. #include namespace std { template && std::is_move_constructible= _v> struct Tuple_impl { T t; template Tuple_impl(U&& u) : t(static_cast(u)) { } }; template struct Tuple_impl { [[no_unique_address]] T t; template Tuple_impl(U&& u) : t(static_cast(u)) { } }; template struct tuple : Tuple_impl... { template tuple(U&&... u) : Tuple_impl(static_cast(u))... { } }; } struct Bar { Bar() =3D default; Bar(int i); Bar(const Bar&) =3D delete; #ifdef MAKE_IT_MOVABLE Bar(Bar&&) =3D default; #endif }; struct ConvertibleToBar { operator Bar(); }; std::tuple fail1() { return {ConvertibleToBar{}}; } static_assert( sizeof(std::tuple) =3D=3D 8 ); If you define MAKE_IT_MOVABLE then the static assertion fails. This shows t= hat the toy implementation above can support construction from ConvertibleToBar= but at the cost of changing layout if a move constructor is added later. I don't think supporting guaranteed elision for non-copyable, non-movable, empty types is a sufficiently important use case to make the layout fragile= .=