https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94062
--- Comment #14 from m.cencora at gmail dot com --- (In reply to Jonathan Wakely from comment #13) > (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 <tuple> > > struct X > { > template<typename T> > X(T) > { static_assert(!std::is_same_v<T,int>); } > }; > > static_assert(std::is_constructible_v<X, int>); > static_assert(std::is_constructible_v<std::tuple<X>, int>); > > std::tuple<X> 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 > functions for overload resolution. > > The standard is pretty clear about that: > > Constraints: the conditions for the function’s participation in overload > resolution > > That's it. It doesn't mean any more than that. Ok, but how is it relevant here? We could provide full definition of well-formed Bar constructors and CovertibleToBar conversion operator, and it will still fail to compile with libstdc++'s tuple. > > 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. But the user didn't introduce any ill-formed code. Implementation did by relying on copy-elision to work in a place where it is not guaranteed by the standard. > > > > > That would mean that std::tuple<Bar> changes layout if you later add a > > > move > > > constructor to Bar. > > > > Not sure how, because you can create std::tuple<Bar> already by using other > > 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<Bar> 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 > support 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 > subobject changes. So that means the std::tuple layout would depend on the > presence of a move constructor. > > #include <type_traits> > > namespace std > { > template<typename T, > bool compress = std::is_empty_v<T> && > std::is_move_constructible_v<T>> > struct Tuple_impl > { > T t; > > template<typename U> > Tuple_impl(U&& u) : t(static_cast<U&&>(u)) { } > }; > > template<typename T> > struct Tuple_impl<T, true> > { > [[no_unique_address]] T t; > > template<typename U> > Tuple_impl(U&& u) : t(static_cast<U&&>(u)) { } > }; > > template<typename... T> > struct tuple : Tuple_impl<T>... > { > template<typename... U> > tuple(U&&... u) > : Tuple_impl<T>(static_cast<U&&>(u))... > { } > }; > } > > struct Bar > { > Bar() = default; > Bar(int i); > Bar(const Bar&) = delete; > #ifdef MAKE_IT_MOVABLE > Bar(Bar&&) = default; > #endif > }; > > struct ConvertibleToBar > { > operator Bar(); > }; > > std::tuple<Bar> fail1() > { > return {ConvertibleToBar{}}; > } > > static_assert( sizeof(std::tuple<Bar, int>) == 8 ); > > If you define MAKE_IT_MOVABLE then the static assertion fails. This shows > that 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. Whether Bar is empty class or not is irrelevant for this bug. Maybe this could be fixed it without breaking the ABI for empty classes scenario, but I see no way around it for non-empty classes.