Skip to content

WString: direct operator overloads instead of StringSumHelper #7781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Mar 21, 2021
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9c47a1c
wip
mcspr Dec 19, 2020
d1d584e
huh, turns out String = 'c' did some weird stuff
mcspr Dec 19, 2020
c9a55f7
style
mcspr Dec 22, 2020
7a9cf16
Merge remote-tracking branch 'origin/master' into string/no-sum-helper
mcspr Dec 22, 2020
14e1e7c
allow "blah" + String, 'c' + String and F("...") + String
mcspr Dec 22, 2020
559a5db
shuffle things into .cpp
mcspr Dec 22, 2020
6dbcfa9
trying to fix arduinojson
mcspr Dec 22, 2020
4683923
fix accidental realloc, add test for operator+
mcspr Dec 23, 2020
6a3fc86
fixup! fix accidental realloc, add test for operator+
mcspr Dec 23, 2020
52c8b37
don't need another branch
mcspr Dec 23, 2020
5c35ae3
template +=(String / char* / numbers) and +(String, numbers / char*)
mcspr Dec 23, 2020
c908321
Merge remote-tracking branch 'origin/master' into string/no-sum-helper
mcspr Dec 23, 2020
425b366
Merge remote-tracking branch 'origin/master' into string/no-sum-helper
mcspr Jan 5, 2021
e553e26
nul after moving (isnt mem always zeroed tho?)
mcspr Jan 6, 2021
73a28d0
check if lhs cant keep before switching to rhs
mcspr Jan 6, 2021
fa00fba
fix String used to store struct data
mcspr Jan 6, 2021
8af5a00
style once more
mcspr Jan 6, 2021
c57ae69
typo
mcspr Jan 6, 2021
ed60441
Merge branch 'master' into string/no-sum-helper
earlephilhower Jan 8, 2021
4b70ead
Merge branch 'master' into string/no-sum-helper
earlephilhower Jan 9, 2021
b4ec910
Merge remote-tracking branch 'origin/master' into string/no-sum-helper
mcspr Jan 27, 2021
95ebd47
Merge remote-tracking branch 'origin/master' into string/no-sum-helper
mcspr Mar 21, 2021
3299833
recover 444002180bca8e36b3014eaf5ecf5e690837b440
mcspr Mar 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions cores/esp8266/StreamString.h
Original file line number Diff line number Diff line change
@@ -236,7 +236,6 @@ class StreamString: public String, public S2Stream
StreamString(const String& string): String(string), S2Stream(this) { }
StreamString(const __FlashStringHelper *str): String(str), 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(unsigned char c, unsigned char base = 10): String(c, base), S2Stream(this) { }
@@ -281,13 +280,6 @@ class StreamString: public String, public S2Stream
resetpp();
return *this;
}

StreamString& operator= (StringSumHelper&& rval)
{
String::operator=(rval);
resetpp();
return *this;
}
};

#endif // __STREAMSTRING_H
141 changes: 65 additions & 76 deletions cores/esp8266/WString.cpp
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <Arduino.h>
#include "Arduino.h"
#include "WString.h"
#include "stdlib_noniso.h"

@@ -56,11 +56,6 @@ String::String(String &&rval) noexcept {
move(rval);
}

String::String(StringSumHelper &&rval) noexcept {
init();
move(rval);
}

String::String(unsigned char value, unsigned char base) {
init();
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) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs.buffer(), rhs.len()))
a.invalidate();
return a;
}
String &String::insert(size_t position, const char *other, size_t other_length) {
if (position > length())
return *this;

StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!cstr || !a.concat(cstr, strlen(cstr)))
a.invalidate();
return a;
}
auto len = length();
auto total = len + other_length;
if (!reserve(total))
return *this;

StringSumHelper &operator +(const StringSumHelper &lhs, char c) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(c))
a.invalidate();
return a;
}
auto left = len - position;
setLen(total);

StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
auto *start = wbuffer() + position;
memmove(start + other_length, start, left);
memmove_P(start, other, other_length);
wbuffer()[total] = '\0';

StringSumHelper &operator +(const StringSumHelper &lhs, int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
return *this;
}

StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String &String::insert(size_t position, const __FlashStringHelper *other) {
auto *p = reinterpret_cast<const char*>(other);
return insert(position, p, strlen_P(p));
}

StringSumHelper &operator +(const StringSumHelper &lhs, long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String &String::insert(size_t position, char other) {
char tmp[2] { other, '\0' };
return insert(position, tmp, 1);
}

StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String &String::insert(size_t position, const char *other) {
return insert(position, other, strlen(other));
}

StringSumHelper &operator +(const StringSumHelper &lhs, long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String &String::insert(size_t position, const String &other) {
return insert(position, other.c_str(), other.length());
}

StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String operator +(const String &lhs, String &&rhs) {
String res;
auto total = lhs.length() + rhs.length();
if (rhs.capacity() > total) {
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, float num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String operator +(String &&lhs, String &&rhs) {
String res;
auto total = lhs.length() + rhs.length();
if ((total > lhs.capacity()) && (total < rhs.capacity())) {
rhs.insert(0, lhs);
res = std::move(rhs);
} else {
lhs += rhs;
rhs.invalidate();
res = std::move(lhs);
}

return res;
}

StringSumHelper &operator +(const StringSumHelper &lhs, double num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
String operator +(char lhs, const String &rhs) {
String res;
res.reserve(rhs.length() + 1);
res += lhs;
res += rhs;
return res;
}

StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs))
a.invalidate();
return a;
String operator +(const char *lhs, const String &rhs) {
String res;
res.reserve(strlen_P(lhs) + rhs.length());
res += lhs;
res += rhs;
return res;
}

/*********************************************/
210 changes: 95 additions & 115 deletions cores/esp8266/WString.h
Original file line number Diff line number Diff line change
@@ -23,21 +23,26 @@
#define String_class_h
#ifdef __cplusplus

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pgmspace.h>

// An inherited class for holding the result of a concatenation. These
// result objects are assumed to be writable by subsequent concatenations.
class StringSumHelper;
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <cctype>

#include <utility>
#include <type_traits>

// an abstract class used as a means to proide a unique pointer type
// but really has no body
class __FlashStringHelper;
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#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
class String {
// use a function pointer to allow for "if (s)" without the
@@ -60,7 +65,6 @@ class String {
String(const String &str);
String(const __FlashStringHelper *str);
String(String &&rval) noexcept;
String(StringSumHelper &&rval) noexcept;
explicit String(char c) {
sso.buff[0] = c;
sso.buff[1] = 0;
@@ -104,8 +108,10 @@ class String {
String &operator =(const char *cstr);
String &operator =(const __FlashStringHelper *str);
String &operator =(String &&rval) noexcept;
String &operator =(StringSumHelper &&rval) noexcept {
return operator =((String &&)rval);
String &operator =(char c) {
char buffer[2] { c, '\0' };
*this = buffer;
return *this;
}

// concatenate (works w/ built-in types)
@@ -130,72 +136,11 @@ class 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)
String &operator +=(const String &rhs) {
template <typename T>
String &operator +=(const T &rhs) {
concat(rhs);
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")
operator StringIfHelperType() const {
@@ -338,6 +283,14 @@ class String {
const char *buffer() const { return wbuffer(); }
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:
void init(void) __attribute__((always_inline)) {
sso.buff[0] = 0;
@@ -359,54 +312,81 @@ class String {
void invalidate(void);
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 __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;
};

class StringSumHelper: public String {
public:
StringSumHelper(const String &s) :
String(s) {
}
StringSumHelper(const char *p) :
String(p) {
}
StringSumHelper(char c) :
String(c) {
}
StringSumHelper(unsigned char num) :
String(num) {
}
StringSumHelper(int num) :
String(num) {
}
StringSumHelper(unsigned int num) :
String(num) {
}
StringSumHelper(long num) :
String(num) {
}
StringSumHelper(unsigned long num) :
String(num) {
}
StringSumHelper(long long num) :
String(num) {
}
StringSumHelper(unsigned long long num) :
String(num) {
}
StringSumHelper(float num) :
String(num) {
}
StringSumHelper(double num) :
String(num) {
}
StringSumHelper(const __FlashStringHelper *s) :
String(s) {
}
};
// concatenation (note that it's done using non-method operators to handle both possible type refs)

inline String operator +(const String &lhs, const String &rhs) {
String res;
res.reserve(lhs.length() + rhs.length());
res += lhs;
res += rhs;
return res;
}

inline String operator +(String &&lhs, const String &rhs) {
lhs += rhs;
return std::move(lhs);
}

String operator +(const String &lhs, String &&rhs);
String operator +(String &&lhs, String &&rhs);

template <typename T,
typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
inline String operator +(const String &lhs, const T &value) {
String res(lhs);
res += value;
return res;
}

template <typename T,
typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
inline String operator +(String &&lhs, const T &value) {
lhs += value;
return std::move(lhs);
}

// `String(char)` is explicit, but we used to have StringSumHelper silently allowing the following:
// `String x; x = 'a' + String('b') + 'c';`
// For comparison, `std::string(char)` does not exist. However, we are allowed to chain `char` as both lhs and rhs

String operator +(char lhs, const String &rhs);

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;
}
Comment on lines +383 to +385
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As to why - the rest of the .cpp, const char* is expected to sometimes be in flash, meaning it is also valid to do PSTR("123") + String(456)


inline String operator +(const __FlashStringHelper *lhs, String &&rhs) {
return std::move(rhs.insert(0, lhs));
}

extern const String emptyString;

39 changes: 39 additions & 0 deletions tests/host/core/test_string.cpp
Original file line number Diff line number Diff line change
@@ -555,3 +555,42 @@ TEST_CASE("Replace and string expansion", "[core][String]")
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));
}
}