Date: Wed, 28 Oct 2026 09:55:21 -0400
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*
}
–Arthur
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*
}
–Arthur
Received on 2026-10-28 13:55:37
