This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++20 status.

### 3076. `basic_string` CTAD ambiguity

Section: 23.4.3.3 [string.cons] Status: C++20 Submitter: Stephan T. Lavavej Opened: 2018-03-03 Last modified: 2021-02-25

Priority: Not Prioritized

View all other issues in [string.cons].

View all issues with C++20 status.

Discussion:

The following code fails to compile for surprising reasons.

>     #include <string>
>     #include <string_view>

>     using namespace std;

>     int main()
>     {
>        string s0;
>        basic_string s1(s0, 1, 1);
>        // WANT: basic_string(const basic_string&, size_type, size_type, const Allocator& = Allocator())
>        // CONFLICT: basic_string(size_type, charT, const Allocator&)

>        basic_string s2("cat"sv, 1, 1);
>        // WANT: basic_string(const T&, size_type, size_type, const Allocator& = Allocator())
>        // CONFLICT: basic_string(size_type, charT, const Allocator&)

>        basic_string s3("cat", 1);
>        // WANT: basic_string(const charT *, size_type, const Allocator& = Allocator())
>        // CONFLICT: basic_string(const charT *, const Allocator&)
>     }

For `s1` and `s2`, the signature `basic_string(size_type, charT, const Allocator&)` participates in CTAD. `size_type` is non-deduced (it will be substituted later, so the compiler can't immediately realize that `s0` or `"cat"sv` are totally non-viable arguments). `charT` is deduced to be `int` (weird, but not the problem). Finally, `Allocator` is deduced to be `int`. Then the compiler tries to substitute for `size_type`, but this ends up giving int to `allocator_traits` in a non-SFINAE context, so compilation fails.

`s3` fails for a slightly different reason. `basic_string(const charT *, const Allocator&)` participates in CTAD, deducing `charT` to be `char` (good) and `Allocator` to be `int`. This is an exact match, which is better than the constructor that the user actually wants (where `int` would need to be converted to `size_type`, which is unsigned). So CTAD deduces `basic_string<char, char_traits<char>, int>`, which is the wrong type.

This problem appears to be unique to `basic_string` and its heavily overloaded set of constructors. I haven't figured out how to fix it by adding (non-greedy) deduction guides. The conflicting constructors are always considered during CTAD, regardless of whether deduction guides are provided that correspond to the desired or conflicting constructors. (That's because deduction guides are preferred as a late tiebreaker in overload resolution; if a constructor provides a better match it will be chosen before tiebreaking.) It appears that we need to constrain the conflicting constructors themselves; this will have no effect on actual usage (where `Allocator` will be an allocator) but will prevent CTAD from considering them for non-allocators. As this is unusual, I believe it deserves a Note. This has been implemented in MSVC.

[2018-3-14 Wednesday evening issues processing; move to Ready]

[2018-06 Rapperswil: Adopted]

Proposed resolution:

This wording is relative to N4727.

1. Edit 23.4.3.3 [string.cons] as indicated:

   >     basic_string(const charT* s, const Allocator& a = Allocator());

   > > -14- Requires: `s` points to an array of at least `traits::length(s) + 1` elements of `charT`.

   > > -15- Effects: Constructs an object of class `basic_string` and determines its initial string value from the array of `charT` of length `traits::length(s)` whose first element is designated by `s`.

   > > -16- Postconditions: `data()` points at the first element of an allocated copy of the array whose first element is pointed at by `s`, `size()` is equal to `traits::length(s)`, and `capacity()` is a value at least as large as `size()`.

   > > -?- Remarks: Shall not participate in overload resolution if `Allocator` is a type that does not qualify as an allocator (24.2.2.1 [container.requirements.general]). [Note: This affects class template argument deduction. — end note]

   >     basic_string(size_type n, charT c, const Allocator& a = Allocator());

   > > -17- Requires: `n < npos`.

   > > -18- Effects: Constructs an object of class `basic_string` and determines its initial string value by repeating the char-like object `c` for all `n` elements.

   > > -19- Postconditions: `data()` points at the first element of an allocated array of `n` elements, each storing the initial value `c`, `size()` is equal to `n`, and `capacity()` is a value at least as large as `size()`.

   > > -?- Remarks: Shall not participate in overload resolution if `Allocator` is a type that does not qualify as an allocator (24.2.2.1 [container.requirements.general]). [Note: This affects class template argument deduction. — end note]