Date: Wed, 28 Oct 2026 12:12:36 -0400
On Wed, Oct 28, 2026 at 11:07 AM Jonathan Wakely <cxx_at_[hidden]> wrote:
> On Wed, 28 Oct 2026 at 13:55, Arthur O'Dwyer via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On Wed, Oct 28, 2026 at 8:56 AM Johannes Kalmbach via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> We stumbled upon this when writing the very last example
>>> ```
>>> void f(std::vector<std::string>);
>>> // Convenience overload for a single string
>>> void f(std::string);
>>>
>>> for which f({"one", "two"}) is somewhat surprisingly ambiguous, as the
>>> string overload is also eligible.
>>> It could also be silently breaking when refactoring an API from
>>> std::vector<std::string> to std::string (because it turned out that one
>>> string was enough).
>>> In that case having a set of constructor arguments that can be used for
>>> both types is somewhat surprising, as it is, to my knowledge, not the case
>>> for any other
>>> constructor there.
>>>
>>
>> The appropriate fix for this isn't to add a deleted constructor overload
>> to `string`; it's to add the `explicit` keyword to `string`'s two-argument
>> constructor.
>>
>> https://quuxplusone.github.io/blog/2023/04/08/most-ctors-should-be-explicit/
>> Now, will there ever be the political will in WG21 to do this? No,
>> definitely not. So I don't think there's anything to be done at the moment.
>>
>> Implicit conversions due to inappropriately non-explicit constructors are
>> one of the major problems of the STL.
>> Here's another example that I saw fairly often back in the day (like
>> 2011-ish). Right when initializer-list constructors were a new thing and
>> nobody really understood how to use them yet, it was a common (anti)pattern
>> to wrap STL container initializer-lists in *two* sets of curly braces.
>> In fact, the way I remember it, often std::array *wouldn't compile*
>> unless you double-wrapped it (because it's a struct containing an array
>> member, and brace elision wasn't fully implemented everywhere yet). This
>> issue was fixed a long time ago now, of course, and single-curly-braces are
>> 100% correct. But back then, I saw a lot of:
>>
>> // https://godbolt.org/z/vs9qzPre9
>> int main() {
>> std::vector<int> v1 = {{ 1, 2, 3 }}; // vector of size 3
>> std::vector<const char*> v2 = {{ "a", "b", "c" }}; // vector of size 3
>> std::vector<std::string> v3 = {{ "a", "b", "c" }}; // vector of size 3
>> std::vector<int> v4 = {{ 1, 2 }}; // vector of size 2
>> std::vector<const char*> v5 = {{ "a", "b" }}; // vector of size 2
>> std::vector<std::string> v6 = {{ "a", "b" }}; *// UB goes boom*
>>
>
> Yes, the problem OP wants to solve for string and string_view is much
> broader than just those two constructors.
> [...]
> Tangentially, initializer-list constructors would have avoided some of
> their problems if we had actually required the braces to be inside parens
> or another set of braces, so that std::vector<int>({1,2}) was how you
> called a constructor with a std::initializer_list, and
> std::vector<int>{1,2} was unambiguously the vector(size_type, const T&)
> constructor. Or we could have just never introduced "uniform" init at all,
> and braces would only work for aggregates and for std::initializer_list
> temporaries, then std::vector<int>{1,2} would just be ill-formed. Sigh.
>
Not quite; we also want brace-initialization to work for types like
`std::pair` that *pretend* to be aggregates.
int core[] = {1,2,3};
std::vector<int> lib[] = {1,2,3}; // should be OK: no syntactic
"penalty" for using vector instead of core-language array
and
struct { int x, y; } core = {1,2};
std::pair<int,int> lib = {1,2}; // should be OK: no syntactic
"penalty" for using pair instead of core-language struct
But a priori there was no reason to permit uniform initialization *with
explicit type-names present*, like `std::vector<int>{1,2}` and
`std::vector<int> v{1,2}`. We could have made uniform initialization a
thing *only* for implicit conversions (that is, for non-explicit
constructions), such that the user would have to choose between
auto x = MyThing(args...);
MyThing x = {args...};
and simply wouldn't be able to write all the present-day variations such as
auto x = MyThing{args...};
MyThing x{args...};
MyThing x(args...); // well, okay, obviously we have to keep support
for this C++98ism forever
Of course at this point in history there's no way to get there from here.
But it would have been nice if we'd somehow figured out two decades ago,
without hindsight, that that's the surface area we really wanted.
–Arthur
> On Wed, 28 Oct 2026 at 13:55, Arthur O'Dwyer via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On Wed, Oct 28, 2026 at 8:56 AM Johannes Kalmbach via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> We stumbled upon this when writing the very last example
>>> ```
>>> void f(std::vector<std::string>);
>>> // Convenience overload for a single string
>>> void f(std::string);
>>>
>>> for which f({"one", "two"}) is somewhat surprisingly ambiguous, as the
>>> string overload is also eligible.
>>> It could also be silently breaking when refactoring an API from
>>> std::vector<std::string> to std::string (because it turned out that one
>>> string was enough).
>>> In that case having a set of constructor arguments that can be used for
>>> both types is somewhat surprising, as it is, to my knowledge, not the case
>>> for any other
>>> constructor there.
>>>
>>
>> The appropriate fix for this isn't to add a deleted constructor overload
>> to `string`; it's to add the `explicit` keyword to `string`'s two-argument
>> constructor.
>>
>> https://quuxplusone.github.io/blog/2023/04/08/most-ctors-should-be-explicit/
>> Now, will there ever be the political will in WG21 to do this? No,
>> definitely not. So I don't think there's anything to be done at the moment.
>>
>> Implicit conversions due to inappropriately non-explicit constructors are
>> one of the major problems of the STL.
>> Here's another example that I saw fairly often back in the day (like
>> 2011-ish). Right when initializer-list constructors were a new thing and
>> nobody really understood how to use them yet, it was a common (anti)pattern
>> to wrap STL container initializer-lists in *two* sets of curly braces.
>> In fact, the way I remember it, often std::array *wouldn't compile*
>> unless you double-wrapped it (because it's a struct containing an array
>> member, and brace elision wasn't fully implemented everywhere yet). This
>> issue was fixed a long time ago now, of course, and single-curly-braces are
>> 100% correct. But back then, I saw a lot of:
>>
>> // https://godbolt.org/z/vs9qzPre9
>> int main() {
>> std::vector<int> v1 = {{ 1, 2, 3 }}; // vector of size 3
>> std::vector<const char*> v2 = {{ "a", "b", "c" }}; // vector of size 3
>> std::vector<std::string> v3 = {{ "a", "b", "c" }}; // vector of size 3
>> std::vector<int> v4 = {{ 1, 2 }}; // vector of size 2
>> std::vector<const char*> v5 = {{ "a", "b" }}; // vector of size 2
>> std::vector<std::string> v6 = {{ "a", "b" }}; *// UB goes boom*
>>
>
> Yes, the problem OP wants to solve for string and string_view is much
> broader than just those two constructors.
> [...]
> Tangentially, initializer-list constructors would have avoided some of
> their problems if we had actually required the braces to be inside parens
> or another set of braces, so that std::vector<int>({1,2}) was how you
> called a constructor with a std::initializer_list, and
> std::vector<int>{1,2} was unambiguously the vector(size_type, const T&)
> constructor. Or we could have just never introduced "uniform" init at all,
> and braces would only work for aggregates and for std::initializer_list
> temporaries, then std::vector<int>{1,2} would just be ill-formed. Sigh.
>
Not quite; we also want brace-initialization to work for types like
`std::pair` that *pretend* to be aggregates.
int core[] = {1,2,3};
std::vector<int> lib[] = {1,2,3}; // should be OK: no syntactic
"penalty" for using vector instead of core-language array
and
struct { int x, y; } core = {1,2};
std::pair<int,int> lib = {1,2}; // should be OK: no syntactic
"penalty" for using pair instead of core-language struct
But a priori there was no reason to permit uniform initialization *with
explicit type-names present*, like `std::vector<int>{1,2}` and
`std::vector<int> v{1,2}`. We could have made uniform initialization a
thing *only* for implicit conversions (that is, for non-explicit
constructions), such that the user would have to choose between
auto x = MyThing(args...);
MyThing x = {args...};
and simply wouldn't be able to write all the present-day variations such as
auto x = MyThing{args...};
MyThing x{args...};
MyThing x(args...); // well, okay, obviously we have to keep support
for this C++98ism forever
Of course at this point in history there's no way to get there from here.
But it would have been nice if we'd somehow figured out two decades ago,
without hindsight, that that's the surface area we really wanted.
–Arthur
Received on 2026-10-28 16:12:52
