<div dir="ltr">Hi All,<div><br></div><div>I will be removing the "reduced-arity-initialization" extension from the uses-allocator constructors in std::tuple. This is a breaking change. I plan to make this change immediately without first deprecating the behavior.</div><div><br></div><div>I'm hoping that usage of this extension with uses-allocator construction is rare and this change will cause minimal breakage.<br></div><div><br></div><div>This will not affect the extension in non-uses-allocator cases. Nor will it affect conforming uses-allocator construction.  This change is needed to fix a number of bugs and to ensure future changes in tuple don't cause more breakage.</div><div><div><br></div></div><div><div>Please let me know if you have any objections.</div><div><br></div><div>Below is a more in-depth description of the problem and solution.</div><div><br></div><div><div>==========</div><div>The Problem</div><div>==========</div><div><div><br></div><div>Libc++'s tuple provides an extension where a tuple can be created using fewer initializers than elements in the tuple. The remaining elements are simply default initialized. Example:</div><div><br></div><div>```</div><div>std::tuple<int, float, std::string> t1(42, 0.f); // #1 default constructs string.</div><div>std::tuple<int, float, std::string> t2(std::allocator_arg, std::allocator<void>{}); // #2 default constructs int, float and string.</div><div>```</div><div><br></div><div>However this optimization causes the uses-allocator overloads to participate in overload resolution when they otherwise shouldn't. In example #1 Clang considers and substitutes into std::tuple(allocator_arg_t, Alloc const&, Up&&...) with [Alloc = float, Up = []].</div><div><br></div><div>This can poison the entire overload set by causing the evaluation of SFINAE not permitted by the standard. For example the following code blows up while substituting into the uses-allocator constructors during the evaluation of std::is_default_constructible<UPtr>.</div><div><br></div><div>```</div><div>void DeleteFn(int*);</div><div>using UPtr = std::unique_ptr<int, void(*)(int*)>;</div><div>std::tuple<UPtr, UPtr> t(UPtr(nullptr, &DeleteFn), UPtr(nullptr, &DeleteFn));  // triggers a static_assert in std::unique_ptr's default ctor.</div><div>```</div><div><br></div><div>========</div><div>Resolution</div><div>========</div><div><br></div><div>The problem is fixed by removing the extension from the uses-allocator constructors. By doing this we can immediately disambiguate the uses-allocator  and regular constructors based only on the arity of the call. This can be used to prevent non-standard evaluation of "dangerous" SFINAE traits like "is_constructible" and "is_convertible".</div><div><br></div><div>Note that the extension will continue to be offered for the regular non-uses-allocator constructors.</div><div><br></div><div>======================</div><div>Other Considered Resolution</div><div>======================</div><div><br></div><div>The other solution I considered was to change the 'allocator_arg_t' parameter to be deduced and</div><div>adding SFINAE to check if the deduced type is convertible to 'allocator_arg_t'. This approach has a number of issues:</div><div><br></div><div>1. Regular overloads would also have add additional SFINAE to check if their parameter pack started with an object that was convertible to allocator_arg_t. This check could break existing code.</div><div>2. Deducing the allocator_arg_t parameter can cause slight semantic differences compared to using a concrete type.</div><div>3. This prevents 'allocator_arg_t' from being stored in a tuple.</div><div><br></div><div>Thanks</div><div>/Eric</div><div><br></div><div><br></div><div><br></div></div></div></div></div>