1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-06-09 03:41:41 +03:00

WString: direct operator overloads instead of StringSumHelper (#7781)

* wip

* huh, turns out String = 'c' did some weird stuff

* style

* allow "blah" + String, 'c' + String and F("...") + String

also, simplify const char* vs. __FlashStringHelper, and just always use _P functions

* shuffle things into .cpp

* trying to fix arduinojson

based on the implementation, we only need to specify that this symbol is a class

* fix accidental realloc, add test for operator+

basic chaining should work just like with master
comparing std::move() buffers won't work though, because we never allow
anything but `const StringSumHelper&` references

* fixup! fix accidental realloc, add test for operator+

* don't need another branch

* template +=(String / char* / numbers) and +(String, numbers / char*)

* nul after moving (isnt mem always zeroed tho?)

* check if lhs cant keep before switching to rhs

* fix String used to store struct data

`cannot bind bit-field '...' to 'signed char&'
`cannot bind bit-field '...' to 'unssigned char&'

noticed in both tasmota and espeasy, where this generates a webpage
content from some status struct containing bitfields

* style once more

* typo

* recover 444002180bca8e36b3014eaf5ecf5e690837b440
This commit is contained in:
Max Prokhorov 2021-03-21 16:40:25 +03:00 committed by GitHub
parent 1b922edad1
commit 0894b514cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 203 additions and 203 deletions

View File

@ -236,7 +236,6 @@ public:
StreamString(const String& string): String(string), S2Stream(this) { } StreamString(const String& string): String(string), S2Stream(this) { }
StreamString(const __FlashStringHelper *str): String(str), S2Stream(this) { } StreamString(const __FlashStringHelper *str): String(str), S2Stream(this) { }
StreamString(String&& string): String(string), S2Stream(this) { } StreamString(String&& string): String(string), S2Stream(this) { }
StreamString(StringSumHelper&& sum): String(sum), S2Stream(this) { }
explicit StreamString(char c): String(c), S2Stream(this) { } explicit StreamString(char c): String(c), S2Stream(this) { }
explicit StreamString(unsigned char c, unsigned char base = 10): String(c, base), S2Stream(this) { } explicit StreamString(unsigned char c, unsigned char base = 10): String(c, base), S2Stream(this) { }
@ -281,13 +280,6 @@ public:
resetpp(); resetpp();
return *this; return *this;
} }
StreamString& operator= (StringSumHelper&& rval)
{
String::operator=(rval);
resetpp();
return *this;
}
}; };
#endif // __STREAMSTRING_H #endif // __STREAMSTRING_H

View File

@ -21,7 +21,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#include <Arduino.h> #include "Arduino.h"
#include "WString.h" #include "WString.h"
#include "stdlib_noniso.h" #include "stdlib_noniso.h"
@ -56,11 +56,6 @@ String::String(String &&rval) noexcept {
move(rval); move(rval);
} }
String::String(StringSumHelper &&rval) noexcept {
init();
move(rval);
}
String::String(unsigned char value, unsigned char base) { String::String(unsigned char value, unsigned char base) {
init(); init();
char buf[1 + 8 * sizeof(unsigned char)]; char buf[1 + 8 * sizeof(unsigned char)];
@ -390,98 +385,92 @@ unsigned char String::concat(const __FlashStringHelper *str) {
} }
/*********************************************/ /*********************************************/
/* Concatenate */ /* Insert */
/*********************************************/ /*********************************************/
StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) { String &String::insert(size_t position, const char *other, size_t other_length) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); if (position > length())
if (!a.concat(rhs.buffer(), rhs.len())) return *this;
a.invalidate();
return a; auto len = length();
auto total = len + other_length;
if (!reserve(total))
return *this;
auto left = len - position;
setLen(total);
auto *start = wbuffer() + position;
memmove(start + other_length, start, left);
memmove_P(start, other, other_length);
wbuffer()[total] = '\0';
return *this;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) { String &String::insert(size_t position, const __FlashStringHelper *other) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); auto *p = reinterpret_cast<const char*>(other);
if (!cstr || !a.concat(cstr, strlen(cstr))) return insert(position, p, strlen_P(p));
a.invalidate();
return a;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, char c) { String &String::insert(size_t position, char other) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); char tmp[2] { other, '\0' };
if (!a.concat(c)) return insert(position, tmp, 1);
a.invalidate();
return a;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) { String &String::insert(size_t position, const char *other) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); return insert(position, other, strlen(other));
if (!a.concat(num))
a.invalidate();
return a;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, int num) { String &String::insert(size_t position, const String &other) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); return insert(position, other.c_str(), other.length());
if (!a.concat(num))
a.invalidate();
return a;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) { String operator +(const String &lhs, String &&rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); String res;
if (!a.concat(num)) auto total = lhs.length() + rhs.length();
a.invalidate(); if (rhs.capacity() > total) {
return a; rhs.insert(0, lhs);
res = std::move(rhs);
} else {
res.reserve(total);
res += lhs;
res += rhs;
rhs.invalidate();
}
return res;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, long num) { String operator +(String &&lhs, String &&rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); String res;
if (!a.concat(num)) auto total = lhs.length() + rhs.length();
a.invalidate(); if ((total > lhs.capacity()) && (total < rhs.capacity())) {
return a; rhs.insert(0, lhs);
res = std::move(rhs);
} else {
lhs += rhs;
rhs.invalidate();
res = std::move(lhs);
}
return res;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) { String operator +(char lhs, const String &rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); String res;
if (!a.concat(num)) res.reserve(rhs.length() + 1);
a.invalidate(); res += lhs;
return a; res += rhs;
return res;
} }
StringSumHelper &operator +(const StringSumHelper &lhs, long long num) { String operator +(const char *lhs, const String &rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); String res;
if (!a.concat(num)) res.reserve(strlen_P(lhs) + rhs.length());
a.invalidate(); res += lhs;
return a; res += rhs;
} return res;
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, double num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs))
a.invalidate();
return a;
} }
/*********************************************/ /*********************************************/

View File

@ -23,14 +23,15 @@
#define String_class_h #define String_class_h
#ifdef __cplusplus #ifdef __cplusplus
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pgmspace.h> #include <pgmspace.h>
// An inherited class for holding the result of a concatenation. These #include <cstdlib>
// result objects are assumed to be writable by subsequent concatenations. #include <cstdint>
class StringSumHelper; #include <cstring>
#include <cctype>
#include <utility>
#include <type_traits>
// an abstract class used as a means to proide a unique pointer type // an abstract class used as a means to proide a unique pointer type
// but really has no body // but really has no body
@ -38,6 +39,10 @@ class __FlashStringHelper;
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer)) #define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define F(string_literal) (FPSTR(PSTR(string_literal))) #define F(string_literal) (FPSTR(PSTR(string_literal)))
// support libraries that expect this name to be available
// replace with `using StringSumHelper = String;` in case something wants this constructible
class StringSumHelper;
// The string class // The string class
class String { class String {
// use a function pointer to allow for "if (s)" without the // use a function pointer to allow for "if (s)" without the
@ -60,7 +65,6 @@ class String {
String(const String &str); String(const String &str);
String(const __FlashStringHelper *str); String(const __FlashStringHelper *str);
String(String &&rval) noexcept; String(String &&rval) noexcept;
String(StringSumHelper &&rval) noexcept;
explicit String(char c) { explicit String(char c) {
sso.buff[0] = c; sso.buff[0] = c;
sso.buff[1] = 0; sso.buff[1] = 0;
@ -104,8 +108,10 @@ class String {
String &operator =(const char *cstr); String &operator =(const char *cstr);
String &operator =(const __FlashStringHelper *str); String &operator =(const __FlashStringHelper *str);
String &operator =(String &&rval) noexcept; String &operator =(String &&rval) noexcept;
String &operator =(StringSumHelper &&rval) noexcept { String &operator =(char c) {
return operator =((String &&)rval); char buffer[2] { c, '\0' };
*this = buffer;
return *this;
} }
// concatenate (works w/ built-in types) // concatenate (works w/ built-in types)
@ -130,72 +136,11 @@ class String {
// if there's not enough memory for the concatenated value, the string // if there's not enough memory for the concatenated value, the string
// will be left unchanged (but this isn't signalled in any way) // will be left unchanged (but this isn't signalled in any way)
String &operator +=(const String &rhs) { template <typename T>
String &operator +=(const T &rhs) {
concat(rhs); concat(rhs);
return *this; return *this;
} }
String &operator +=(const char *cstr) {
concat(cstr);
return *this;
}
String &operator +=(char c) {
concat(c);
return *this;
}
String &operator +=(unsigned char num) {
concat(num);
return *this;
}
String &operator +=(int num) {
concat(num);
return *this;
}
String &operator +=(unsigned int num) {
concat(num);
return *this;
}
String &operator +=(long num) {
concat(num);
return *this;
}
String &operator +=(unsigned long num) {
concat(num);
return *this;
}
String &operator +=(long long num) {
concat(num);
return *this;
}
String &operator +=(unsigned long long num) {
concat(num);
return *this;
}
String &operator +=(float num) {
concat(num);
return *this;
}
String &operator +=(double num) {
concat(num);
return *this;
}
String &operator +=(const __FlashStringHelper *str) {
concat(str);
return *this;
}
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr);
friend StringSumHelper &operator +(const StringSumHelper &lhs, char c);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, long long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, float num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, double num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
// comparison (only works w/ Strings and "strings") // comparison (only works w/ Strings and "strings")
operator StringIfHelperType() const { operator StringIfHelperType() const {
@ -338,6 +283,14 @@ class String {
const char *buffer() const { return wbuffer(); } const char *buffer() const { return wbuffer(); }
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
// concatenation is done via non-member functions
// make sure we still have access to internal methods, since we optimize based on capacity of both sides and want to manipulate internal buffers directly
friend String operator +(const String &lhs, String &&rhs);
friend String operator +(String &&lhs, String &&rhs);
friend String operator +(char lhs, String &&rhs);
friend String operator +(const char *lhs, String &&rhs);
friend String operator +(const __FlashStringHelper *lhs, String &&rhs);
protected: protected:
void init(void) __attribute__((always_inline)) { void init(void) __attribute__((always_inline)) {
sso.buff[0] = 0; sso.buff[0] = 0;
@ -359,54 +312,81 @@ class String {
void invalidate(void); void invalidate(void);
unsigned char changeBuffer(unsigned int maxStrLen); unsigned char changeBuffer(unsigned int maxStrLen);
// copy and move // copy or insert at a specific position
String &copy(const char *cstr, unsigned int length); String &copy(const char *cstr, unsigned int length);
String &copy(const __FlashStringHelper *pstr, unsigned int length); String &copy(const __FlashStringHelper *pstr, unsigned int length);
String &insert(size_t position, char);
String &insert(size_t position, const char *);
String &insert(size_t position, const __FlashStringHelper *);
String &insert(size_t position, const char *, size_t length);
String &insert(size_t position, const String &);
// rvalue helper
void move(String &rhs) noexcept; void move(String &rhs) noexcept;
}; };
class StringSumHelper: public String { // concatenation (note that it's done using non-method operators to handle both possible type refs)
public:
StringSumHelper(const String &s) : inline String operator +(const String &lhs, const String &rhs) {
String(s) { String res;
} res.reserve(lhs.length() + rhs.length());
StringSumHelper(const char *p) : res += lhs;
String(p) { res += rhs;
} return res;
StringSumHelper(char c) : }
String(c) {
} inline String operator +(String &&lhs, const String &rhs) {
StringSumHelper(unsigned char num) : lhs += rhs;
String(num) { return std::move(lhs);
} }
StringSumHelper(int num) :
String(num) { String operator +(const String &lhs, String &&rhs);
} String operator +(String &&lhs, String &&rhs);
StringSumHelper(unsigned int num) :
String(num) { template <typename T,
} typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
StringSumHelper(long num) : inline String operator +(const String &lhs, const T &value) {
String(num) { String res(lhs);
} res += value;
StringSumHelper(unsigned long num) : return res;
String(num) { }
}
StringSumHelper(long long num) : template <typename T,
String(num) { typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
} inline String operator +(String &&lhs, const T &value) {
StringSumHelper(unsigned long long num) : lhs += value;
String(num) { return std::move(lhs);
} }
StringSumHelper(float num) :
String(num) { // `String(char)` is explicit, but we used to have StringSumHelper silently allowing the following:
} // `String x; x = 'a' + String('b') + 'c';`
StringSumHelper(double num) : // For comparison, `std::string(char)` does not exist. However, we are allowed to chain `char` as both lhs and rhs
String(num) {
} String operator +(char lhs, const String &rhs);
StringSumHelper(const __FlashStringHelper *s) :
String(s) { inline String operator +(char lhs, String &&rhs) {
} return std::move(rhs.insert(0, lhs));
}; }
// both `char*` and `__FlashStringHelper*` are implicitly converted into `String()`, calling the `operator+(const String& ...);`
// however, here we:
// - do an automatic `reserve(total length)` for the resulting string
// - possibly do rhs.insert(0, ...), when &&rhs capacity could fit both
String operator +(const char *lhs, const String &rhs);
inline String operator +(const char *lhs, String &&rhs) {
return std::move(rhs.insert(0, lhs));
}
inline String operator +(const __FlashStringHelper *lhs, const String &rhs) {
return reinterpret_cast<const char*>(lhs) + rhs;
}
inline String operator +(const __FlashStringHelper *lhs, String &&rhs) {
return std::move(rhs.insert(0, lhs));
}
extern const String emptyString; extern const String emptyString;

View File

@ -555,3 +555,42 @@ TEST_CASE("Replace and string expansion", "[core][String]")
REQUIRE(l.length() == strlen(buff)); REQUIRE(l.length() == strlen(buff));
} }
} }
TEST_CASE("String chaining", "[core][String]")
{
const char* chunks[] {
"~12345",
"67890",
"qwertyuiopasdfghjkl",
"zxcvbnm"
};
String all;
for (auto* chunk : chunks) {
all += chunk;
}
// make sure we can chain a combination of things to form a String
REQUIRE((String(chunks[0]) + String(chunks[1]) + String(chunks[2]) + String(chunks[3])) == all);
REQUIRE((chunks[0] + String(chunks[1]) + F(chunks[2]) + chunks[3]) == all);
REQUIRE((String(chunks[0]) + F(chunks[1]) + F(chunks[2]) + String(chunks[3])) == all);
REQUIRE(('~' + String(&chunks[0][0] + 1) + chunks[1] + String(chunks[2]) + F(chunks[3])) == all);
REQUIRE((String(chunks[0]) + '6' + (&chunks[1][0] + 1) + String(chunks[2]) + F(chunks[3])) == all);
// these are still invalid (and also cannot compile at all):
// - `F(...)` + `F(...)`
// - `F(...)` + `const char*`
// - `const char*` + `F(...)`
// we need `String()` as either rhs or lhs
// ensure chaining reuses the buffer
// (internal details...)
{
String tmp(chunks[3]);
tmp.reserve(2 * all.length());
auto* ptr = tmp.c_str();
String result("~1" + String(&chunks[0][0] + 2) + F(chunks[1]) + chunks[2] + std::move(tmp));
REQUIRE(result == all);
REQUIRE(static_cast<const void*>(result.c_str()) == static_cast<const void*>(ptr));
}
}