Date: Wed, 28 Oct 2026 13:29:22 +0100
The first one breaks the following:
return std::string(std::istreambuf_iterator(...), {});
Maaaybe it could be resolved by doing
template <typename I1, typename I2=I1> requires std::same_as<I1, I2>
basic_string(I1, I2);
but I am not entirely sure if we won't find other legal places it might
break.
On Wed, Oct 28, 2026, 13:19 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
>
return std::string(std::istreambuf_iterator(...), {});
Maaaybe it could be resolved by doing
template <typename I1, typename I2=I1> requires std::same_as<I1, I2>
basic_string(I1, I2);
but I am not entirely sure if we won't find other legal places it might
break.
On Wed, Oct 28, 2026, 13:19 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 12:29:37
