In C and C++, char serves double-duty. It represents both a character and a number. It is considered an integral type, which means that it participates in implicit integer promotion to many other integral types, as well as implicit conversion from other integral types.
Because char is overloaded, it is impossible at the level of a function's interface to know if a user passed in an actual character (a character literal or a variable of type char) or a numeric literal that just so happens to be small enough to fit into a char.
As such, when using char in an interface, one must be careful of accidental conflicts with what the user is providing.
Sequence container types (types that hold a number of elements in a sequence unrelated to the values of those elements) usually have a constructor that takes a count of Ts. This is the number of elements to create in the sequence, constructed via value initialization (there is also has a version that takes a T which is used to copy-initialize these elements, which is what you used).
But basic_string is special; it doesn't have such a constructor. If you tried to do std::string s(4);, you would get a compile error.
However, if you make the change you want, std::string s(4); would compile and execute. But it would not give you a sequence of 4 value-initialized characters. It would give you a string containing a single character with a value of 4. This is because the integer literal 4 can be converted to a char implicitly.
It's bad enough that basic_string is not consistent with the expectations of the common sequence container interface. But to actively make it compile but have radically different behavior would be way worse.
Furthermore, you can use list initialization to get what you want more explicitly:
std::string s1{some_char};
std::string s2 = {4}; //converts 4 to `char`
std::string s3 = {other_char};
List initialization is how we initialize containers of T with a sequence of Ts.