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

### 2758. `std::string{}.assign("ABCDE", 0, 1)` is ambiguous

Section: 21.3.3.7.3 [string.assign] Status: C++17 Submitter: Marshall Clow Opened: 2016-07-30 Last modified: 2020-09-06

Priority: 1

View all other issues in [string.assign].

View all issues with C++17 status.

Discussion:

Before C++17, we had the following signature to `std::basic_string`:

>     basic_string&
>       assign(const basic_string& str, size_type pos, size_type n = npos);

Unlike most of the other member functions on `std::basic_string`, there were not corresponding versions that take a `charT*` or `(charT *, size)`.

In p0254r2, we (I) added:

>     basic_string&
>       assign(basic_string_view<charT, traits> sv, size_type pos, size_type n = npos);

which made the code above ambiguous. There are two conversions from "`const charT*`", one to `basic_string`, and the other to `basic_string_view`, and they're both equally good (in the view of the compiler).

This ambiguity also occurs with the calls

>     insert(size_type pos1, const basic_string& str,             size_type pos2, size_type n = npos);
>     insert(size_type pos1, basic_string_view<charT, traits> sv, size_type pos2, size_type n = npos);

but I will file a separate issue (2757) for that.

A solution is to add even more overloads to `assign`, to make it match all the other member functions of `basic_string`, which come in fours (`string`, `pointer`, `pointer + size`, `string_view`).

[2016-08-03, Chicago, Robert Douglas provides wording]

[2016-08-05, Tim Song comments]

On the assumption that the `basic_string` version is untouchable, I like the updated P/R, with a couple comments:

1. If it's constraining on `is_convertible` to `basic_string_view`, then I think it should take by reference to avoid copying `T`, which can be arbitrarily expensive. Both `const T&` and `T&&` should work; the question is whether to accommodate non-const `operator basic_string_view()`s (which arguably shouldn't exist).

2. Minor issue: `compare` tests `is_convertible` and then uses direct-initialization syntax (which is `is_constructible`). They should match, because it's possible to have `basic_string_view sv = t;` succeed yet `basic_string_view sv(t);` fail.

[2016-08-05, Chicago LWG]

1. breaking ABI was a non-starter
2. we want to convert `char const*` to `basic_string_view` if possible
3. we want to take the original type by reference, to avoid new user string types from being copied
4. it is unfortunate that each length of string literal will generate a new entry in the symbol table

Given feedback from discussion, we wish to go with the `is_convertible_v` method, but change:

1. the function parameter type to `T const&` for each overload
2. the parameter type in `is_convertible_v` to `T const&`
3. the initialization of `sv(t)` to `sv = t`

[2016-08, Chicago]

Fri PM: Move to Tentatively Ready

[2016-08-16, Jonathan Wakely reopens]

The P/R is not correct, the new overloads get chosen in preference to the overloads taking `const char*` when passed a `char*`:

>     #include <string>

>     int main () {
>       std::string str("a");
>       char c = 'b';
>       str.replace(0, 1, &c, 1);
>       if (str[0] != 'b')
>         __builtin_abort();
>     }

With the resolution of 2758 this is now equivalent to:

>     str.replace(0, 1, string_view{&c, 1}, 1);

which replaces the character with `string_view{"b", 1}.substr(1, npos)` i.e. an empty string.

The SFINAE constraints need to disable the new overloads for (at least) the case of non-const `value_type*` arguments.

I've implemented an alternative resolution, which would be specified as:

> Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true` and `is_convertible_v<const T&, const charT*>` is `false`.

All the overloads have the same problem.

This program prints no output in C++14, and we should make sure it prints no output in C++17 too:

>     #include <string>

>     int main()
>     {
>       std::string str("a");
>       char c[1] = { 'b' };
>       str.append(c, 1);
>       if (str != "ab")
>         puts("bad append");
>       str.assign(c, 1);
>       if (str != "b")
>         puts("bad assign");
>       str.insert(0, c, 1);
>       if (str != "bb")
>         puts("bad insert");
>       str.replace(0, 2, c, 1);
>       if (str != "b")
>         puts("bad replace");
>       if (str.compare(0, 1, c, 1))
>         puts("bad compare");
>     }

Ville and I considered "`is_same_v<decay_t<T>, char*>` is `false`" but that would still select the wrong overload for an array of const `char`, because the new function template would be preferred to doing an array-to-pointer conversion to call the old overload.

[2016-09-09 Issues Resolution Telecon]

Ville to provide updated wording; Marshall to implement

[2016-10-05]

Ville provides revised wording.

[2016-10 Telecon]

Ville's wording has been implemented in libstdc++ and libc++. Move this to Tentatively Ready and 2757 to Tentatively Resolved

Previous resolution [SUPERSEDED]:

> This wording is relative to N4606.

> 1. In 21.3.3 [basic.string] modify the synopsis for `basic_string` as follows:

>    >     namespace std {
>    >       template<class charT, class traits = char_traits<charT>,
>    >         class Allocator = allocator<charT>>
>    >       class basic_string {
>    >       public:
>    >         […]
>    >         template<class T>
>    >         basic_string& append(basic_string_view<charT, traits> svconst T& t,
>    >                              size_type pos, size_type n = npos);
>    >         […]
>    >         template<class T>
>    >         basic_string& assign(basic_string_view<charT, traits> svconst T& t,
>    >                              size_type pos, size_type n = npos);
>    >         […]
>    >         template<class T>
>    >         basic_string& insert(size_type pos1, basic_string_view<charT, traits> svconst T& t,
>    >                              size_type pos2, size_type n = npos);
>    >         […]
>    >         template<class T>
>    >         basic_string& replace(size_type pos1, size_type n1,
>    >                               basic_string_view<charT, traits> svconst T& t,
>    >                               size_type pos2, size_type n2 = npos);
>    >         […]
>    >         template<class T>
>    >         int compare(size_type pos1, size_type n1,
>    >                     basic_string_view<charT, traits> svconst T& t,
>    >                     size_type pos2, size_type n2 = npos) const;
>    >         […]
>    >       };
>    >     }

> 2. In 21.3.3.7.2 [string.append], modify `basic_string_view` overload as follows:

>    >     template<class T>
>    >     basic_string& append(basic_string_view<charT, traits> svconst T& t,
>    >                          size_type pos, size_type n = npos);

>    > > -7- Throws: `out_of_range` if `pos > sv.size()`.

>    > > -8- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to append as the smaller of `n` and `sv.size() - pos` and calls `append(sv.data() + pos, rlen)`.

>    > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true`.

>    > > -9- Returns: `*this`.

> 3. In 21.3.3.7.3 [string.assign], modify `basic_string_view` overload as follows:

>    >     template<class T>
>    >     basic_string& assign(basic_string_view<charT, traits> svconst T& t,
>    >                          size_type pos, size_type n = npos);

>    > > -9- Throws: `out_of_range` if `pos > sv.size()`.

>    > > -10- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to assign as the smaller of `n` and `sv.size() - pos` and calls `assign(sv.data() + pos, rlen)`.

>    > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true`.

>    > > -11- Returns: `*this`.

> 4. In 21.3.3.7.4 [string.insert], modify `basic_string_view` overload as follows:

>    >     template<class T>
>    >     basic_string& insert(size_type pos1, basic_string_view<charT, traits> svconst T& t,
>    >                          size_type pos2, size_type n = npos);

>    > > -6- Throws: `out_of_range` if `pos1 > size()` or `pos2 > sv.size()`.

>    > > -7- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to assign as the smaller of `n` and `sv.size() - pos2` and calls `insert(pos1, sv.data() + pos2, rlen)`.

>    > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true`.

>    > > -8- Returns: `*this`.

> 5. In 21.3.3.7.6 [string.replace], modify `basic_string_view` overload as follows:

>    >     template<class T>
>    >     basic_string& replace(size_type pos1, size_type n1,
>    >                          basic_string_view<charT, traits> svconst T& t,
>    >                          size_type pos2, size_type n2 = npos);

>    > > -6- Throws: `out_of_range` if `pos1 > size()` or `pos2 > sv.size()`.

>    > > -7- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to be inserted as the smaller of `n2` and `sv.size() - pos2` and calls `replace(pos1, n1, sv.data() + pos2, rlen)`.

>    > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true`.

>    > > -8- Returns: `*this`.

> 6. In 21.3.3.8.4 [string.compare], modify `basic_string_view` overload as follows:

>    >     template<class T>
>    >     int compare(size_type pos1, size_type n1,
>    >                 basic_string_view<charT, traits> svconst T& t,
>    >                 size_type pos2, size_type n2 = npos) const;

>    > > -4- Effects: Equivalent to:

>    > > >     {
>    > > >       basic_string_view<charT, traits> sv = t;
>    > > >       return basic_string_view<charT, traits>(this.data(), pos1, n1).compare(sv, pos2, n2);
>    > > >     }

>    > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true`.

Proposed resolution:

This wording is relative to N4606.

1. In 21.3.3 [basic.string] modify the synopsis for `basic_string` as follows:

   >     namespace std {
   >       template<class charT, class traits = char_traits<charT>,
   >         class Allocator = allocator<charT>>
   >       class basic_string {
   >       public:
   >         […]
   >         template<class T>
   >         basic_string& append(basic_string_view<charT, traits> svconst T& t,
   >                              size_type pos, size_type n = npos);
   >         […]
   >         template<class T>
   >         basic_string& assign(basic_string_view<charT, traits> svconst T& t,
   >                              size_type pos, size_type n = npos);
   >         […]
   >         template<class T>
   >         basic_string& insert(size_type pos1, basic_string_view<charT, traits> svconst T& t,
   >                              size_type pos2, size_type n = npos);
   >         […]
   >         template<class T>
   >         basic_string& replace(size_type pos1, size_type n1,
   >                               basic_string_view<charT, traits> svconst T& t,
   >                               size_type pos2, size_type n2 = npos);
   >         […]
   >         template<class T>
   >         int compare(size_type pos1, size_type n1,
   >                     basic_string_view<charT, traits> svconst T& t,
   >                     size_type pos2, size_type n2 = npos) const;
   >         […]
   >       };
   >     }

2. In 21.3.3.7.2 [string.append], modify `basic_string_view` overload as follows:

   >     template<class T>
   >     basic_string& append(basic_string_view<charT, traits> svconst T& t,
   >                          size_type pos, size_type n = npos);

   > > -7- Throws: `out_of_range` if `pos > sv.size()`.

   > > -8- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to append as the smaller of `n` and `sv.size() - pos` and calls `append(sv.data() + pos, rlen)`.

   > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true` and `is_convertible_v<const T&, const charT*>` is `false`.

   > > -9- Returns: `*this`.

3. In 21.3.3.7.3 [string.assign], modify `basic_string_view` overload as follows:

   >     template<class T>
   >     basic_string& assign(basic_string_view<charT, traits> svconst T& t,
   >                          size_type pos, size_type n = npos);

   > > -9- Throws: `out_of_range` if `pos > sv.size()`.

   > > -10- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to assign as the smaller of `n` and `sv.size() - pos` and calls `assign(sv.data() + pos, rlen)`.

   > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true` and `is_convertible_v<const T&, const charT*>` is `false`.

   > > -11- Returns: `*this`.

4. In 21.3.3.7.4 [string.insert], modify `basic_string_view` overload as follows:

   >     template<class T>
   >     basic_string& insert(size_type pos1, basic_string_view<charT, traits> svconst T& t,
   >                          size_type pos2, size_type n = npos);

   > > -6- Throws: `out_of_range` if `pos1 > size()` or `pos2 > sv.size()`.

   > > -7- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to assign as the smaller of `n` and `sv.size() - pos2` and calls `insert(pos1, sv.data() + pos2, rlen)`.

   > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true` and `is_convertible_v<const T&, const charT*>` is `false`.

   > > -8- Returns: `*this`.

5. In 21.3.3.7.6 [string.replace], modify `basic_string_view` overload as follows:

   >     template<class T>
   >     basic_string& replace(size_type pos1, size_type n1,
   >                          basic_string_view<charT, traits> svconst T& t,
   >                          size_type pos2, size_type n2 = npos);

   > > -6- Throws: `out_of_range` if `pos1 > size()` or `pos2 > sv.size()`.

   > > -7- Effects: Creates a variable, `sv`, as if by `basic_string_view<charT, traits> sv = t`. Determines the effective length `rlen` of the string to be inserted as the smaller of `n2` and `sv.size() - pos2` and calls `replace(pos1, n1, sv.data() + pos2, rlen)`.

   > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true` and `is_convertible_v<const T&, const charT*>` is `false`.

   > > -8- Returns: `*this`.

6. In 21.3.3.8.4 [string.compare], modify `basic_string_view` overload as follows:

   > [Drafting note: The wording changes below are for the same part of the standard as 2771. However, they do not conflict. This one changes the definition of the routine, while the other changes the "Effects". — end drafting note]

   >     template<class T>
   >     int compare(size_type pos1, size_type n1,
   >                 basic_string_view<charT, traits> svconst T& t,
   >                 size_type pos2, size_type n2 = npos) const;

   > > -4- Effects: Equivalent to:

   > > >     {
   > > >       basic_string_view<charT, traits> sv = t;
   > > >       return basic_string_view<charT, traits>(this.data(), pos1, n1).compare(sv, pos2, n2);
   > > >     }

   > > -?- Remarks: This function shall not participate in overload resolution unless `is_convertible_v<const T&, basic_string_view<charT, traits>>` is `true` and `is_convertible_v<const T&, const charT*>` is `false`.