C++ Logo

std-proposals

Advanced search

[std-proposals] Pre-proposal: Constraining accidental scalar append to std::basic_string

From: 牧羊少年 <jiuwoyoubing_at_[hidden]>
Date: Sun, 17 May 2026 23:44:31 +0800
Dear std-proposals,

I would like to get feedback on a possible library proposal for
std::basic_string.
Problem

Today, std::basic_string<CharT>::operator+=(CharT) makes expressions such
as the following well-formed:

std::string s;
s += 65; // appends char(65), commonly 'A', not "65"
s += true; // appends char(1)
s += 300; // appends a converted char value

This is surprising because the syntax reads like string concatenation, but
the operation is actually single-code-unit append through implicit
scalar-to-CharT conversion.

By contrast:

std::string s;
auto x = s + 65; // ill-formed

is rejected in ordinary implementations, because the non-member operator+
templates cannot deduce a consistent CharT from both the std::string
argument and the int argument.

So we have an unfortunate asymmetry:

s += 65; // accepted: appends one char
s + 65; // rejected

I do not think the problem is that operator+=(CharT) appends one character.
That operation is useful and should remain valid:

s += 'A'; // should remain OK
s.push_back('A');

The problem is that non-character scalar values can accidentally bind to
the CharT overload.
Prior related work

This is closely related to P2037R1, “String’s gratuitous assignment”, which
discussed the analogous issue for:

std::string s;
s = 50; // assigns one character, not "50"

This proposal idea is narrower: it concerns operator+=, not assignment.

There is also precedent for making dangerous string implicit behavior
ill-formed. P2166R1 prohibited constructing basic_string and
basic_string_view from nullptr, turning a common runtime hazard into a
compile-time error.
Proposed direction

The proposed direction is not to remove or change operator+=(CharT).

Instead, preserve:

std::string s;

s += 'A'; // OK
s += static_cast<char>(65); // OK: explicit code-unit intent
s += std::to_string(65); // OK: explicit textual intent
s += std::format("{}", 65); // OK

but reject accidental scalar append:

std::string s;

s += 65; // proposed: ill-formed
s += 0; // proposed: ill-formed
s += true; // proposed: ill-formed
s += nullptr; // proposed: ill-formed

A possible library-only mechanism would be to add deleted overloads to
basic_string, for example conceptually:

template<class T>
  requires (!same_as<remove_cvref_t<T>, CharT> &&
            convertible_to<T, CharT> &&
            /* not a string-like argument */)
basic_string& operator+=(T&&) = delete;

basic_string& operator+=(nullptr_t) = delete;

The exact constraints are the main design question. The intent is to catch
accidental scalar-to-character conversion while preserving normal
string-like append operations.
Why not change operator+=(int) to append decimal text?

That would introduce implicit formatting into operator+=, which seems
worse. If the programmer wants textual formatting, the spelling should be
explicit:

s += std::to_string(n);
s += std::format("{}", n);

If the programmer wants to append a code unit derived from an integer, that
should also be explicit:

s.push_back(static_cast<char>(n));

Compatibility

This would be source-breaking for code that intentionally writes:

int ch = std::getchar();
if (ch != EOF)
    s += ch;

The migration is straightforward:

s += static_cast<char>(ch);
// or
s.push_back(static_cast<char>(ch));

However, the real question is whether the breakage rate is acceptable. I
would expect corpus data and implementation experiments to be necessary
before this could become a serious paper.

A narrower alternative would be to reject only arithmetic and enumeration
types other than CharT, rather than every type convertible to CharT.
Questions for the list

   1.

   Is the asymmetry between s += 65 and s + 65 considered a defect worth
   addressing, or only an unfortunate but acceptable historical artifact?
   2.

   Would a deleted-overload approach be acceptable in principle?
   3.

   Should the rejection cover all non-CharT types convertible to CharT, or
   only arithmetic and enumeration types?
   4.

   Should this be limited to operator+=, or should a future paper also
   revisit operator=(CharT), as discussed in P2037R1?
   5.

   Should push_back(CharT) remain unconstrained, since its name already
   clearly denotes single-code-unit append?
   6.

   Would implementers be willing to experiment with a warning or extension
   mode to gather compatibility data?

My current position is that operator+=(CharT) should remain valid, but
accidental scalar append through operator+= should become ill-formed or at
least diagnosable. The goal is to make the programmer choose explicitly
between “append a code unit” and “append the textual representation of a
value”.

Feedback would be appreciated.

Best regards,

Qirong Zhang

Received on 2026-05-17 15:44:39