Date: Wed, 28 Oct 2026 12:10:01 -0400
Was it intended to break the following?
const char a[]="abc";
std::string s(a,a);
Additionally, consider different encoding prefixes:
std::string u(u8"abc",u8"def");
The proposed constructor would not prohibit this.
On Wed, Oct 28, 2026 at 8:19 AM Johannes Kalmbach via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> 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
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
const char a[]="abc";
std::string s(a,a);
Additionally, consider different encoding prefixes:
std::string u(u8"abc",u8"def");
The proposed constructor would not prohibit this.
On Wed, Oct 28, 2026 at 8:19 AM Johannes Kalmbach via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> 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
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2026-10-28 16:12:10
