Date: Wed, 28 Oct 2026 13:19:14 +0100
Hi,
The following snippet compiles, but invokes UB, because overload resolution
treats the decayed pointers to the two string literals as begin an end of
the string:
`std::string s{"undefined", "behavior"}`
I would propose disallowing this antipattern, similar to P2166 disallows
string construction from `nullptr`.
Fundamentally there are two approaches:
1. Deleting that constructor explicitly
```
template <size_t N, size_t M>
basic_string(const CharT(&a)[N], const CharT(&b)[M]) = delete;
```
This works in the sense, that compilation never suceeds, but for two
literals with the same length the reason will be an ambiguous overload, and
not the deleted function. That could, if desired, be addressed by modifying
the constructor from two iterators:
```
// as is
template <typename It>
basic_string(It, It); // leads to ambiguity
// new, but semantics the same for existing correct programs
template <typename I1, typename I2> requires std::same_as<I1, I2>
basic_string(I1, I2);
2. Constrain the constructor from two iterators to never accept two
literals. Unfortunately that is not easy, as the iterators are taken by
value, and so the constraint would always see the decayed type `const
CharT*`.
The only way to make that work would be to let that constructor take
references (assume a concept `array_like` that returns true for (reference
to) plain C-arrays:
```
template <typename I1, typename I2> requires (std::same_as<I1, I2> &&
!array_like<I1> && !array_like<I2>)
basic_string(I1&&, I2&&);
Version 2. has the disadvantage of being more intrusive wrt preexisting
constructors, and by using the antipattern of references to iterators.
It however has the advante of making the following pattern work (which is
how I originally stumbled upon this):
```
void f(std::vector<std::string>);
// Convenience overload for a single string
void f(std::string);
```
Where `f({"one", "two"})` is ambiguous today (and stays so with the deleted
constructor from 1.), but works as expected with a constrained
constructor from 2.
Note:
There is a constructor to `basic_string_view` that suffers from exactly the
same issue, and can and would be addressed in exactly the same way.
I am looking forward to you feedback.
Best regards
Johannes
The following snippet compiles, but invokes UB, because overload resolution
treats the decayed pointers to the two string literals as begin an end of
the string:
`std::string s{"undefined", "behavior"}`
I would propose disallowing this antipattern, similar to P2166 disallows
string construction from `nullptr`.
Fundamentally there are two approaches:
1. Deleting that constructor explicitly
```
template <size_t N, size_t M>
basic_string(const CharT(&a)[N], const CharT(&b)[M]) = delete;
```
This works in the sense, that compilation never suceeds, but for two
literals with the same length the reason will be an ambiguous overload, and
not the deleted function. That could, if desired, be addressed by modifying
the constructor from two iterators:
```
// as is
template <typename It>
basic_string(It, It); // leads to ambiguity
// new, but semantics the same for existing correct programs
template <typename I1, typename I2> requires std::same_as<I1, I2>
basic_string(I1, I2);
2. Constrain the constructor from two iterators to never accept two
literals. Unfortunately that is not easy, as the iterators are taken by
value, and so the constraint would always see the decayed type `const
CharT*`.
The only way to make that work would be to let that constructor take
references (assume a concept `array_like` that returns true for (reference
to) plain C-arrays:
```
template <typename I1, typename I2> requires (std::same_as<I1, I2> &&
!array_like<I1> && !array_like<I2>)
basic_string(I1&&, I2&&);
Version 2. has the disadvantage of being more intrusive wrt preexisting
constructors, and by using the antipattern of references to iterators.
It however has the advante of making the following pattern work (which is
how I originally stumbled upon this):
```
void f(std::vector<std::string>);
// Convenience overload for a single string
void f(std::string);
```
Where `f({"one", "two"})` is ambiguous today (and stays so with the deleted
constructor from 1.), but works as expected with a constrained
constructor from 2.
Note:
There is a constructor to `basic_string_view` that suffers from exactly the
same issue, and can and would be addressed in exactly the same way.
I am looking forward to you feedback.
Best regards
Johannes
Received on 2026-10-28 12:19:29
