I have a very simple fixed-buffer string class. I am targeting embedded and don't want dynamic allocations. I am including it for reference, but the question is more general.
template <auto MaxSizeT = size_t(16)>
class FixedString
{
public:
using size_type = decltype(MaxSizeT);
constexpr static size_type maxSize = MaxSizeT;
FixedString() : length(0)
{
data[0] = '\0'; // Ensure null termination
}
FixedString(const char* inData) { assignStr(inData); }
FixedString(const FixedString& other) { assignStr(other); }
char data[maxSize + 1]; // Add space for null terminator
size_type length; // Current length of the string
FixedString operator+(const FixedString& other) const
{
FixedString result{*this};
result += other;
return result;
}
FixedString& operator+=(const FixedString& other)
{
if (length >= maxSize)
{
return *this;
}
size_type const space = maxSize - length;
auto copyLen = mcu::nmin(space, other.length);
memcpy(&data[length], other.data, copyLen);
length += copyLen;
data[length] = 0;
return *this;
}
FixedString& operator=(const FixedString& other)
{
assignStr(other);
return *this;
}
template <auto OtherMaxSizeT = size_t(16)>
FixedString& operator=(const FixedString<OtherMaxSizeT>& other)
{
assignStr(other.data, other.length);
return *this;
}
FixedString& operator=(const char* other)
{
assignStr(other);
return *this;
}
bool operator==(const FixedString& other) const
{
return length == other.length && memcmp(data, other.data, length) == 0;
}
bool operator==(const char* other) const
{
auto const otherLen = strlen(other);
if (otherLen != length)
{
return false;
}
else
{
return memcmp(&data[0], other, length) == 0;
}
}
void assignStr(const char* inData)
{
auto len = strlen(inData);
assignStr(inData, len);
}
void assignStr(const char* inData, size_t len)
{
if (len > maxSize)
{
len = maxSize;
}
memcpy(data, inData, len);
data[len] = 0;
length = len;
}
void assignStr(const FixedString& other)
{
memcpy(data, other.data, maxSize);
length = other.length;
data[length] = 0;
}
char& operator[](size_t index) { return index < length ? data[index] : data[0]; }
const char& operator[](size_t index) const
{
// Bounds check: return a dummy value if out of range
static char dummy = '\0';
return index < length ? data[index] : dummy;
}
char* begin() { return data; }
char* end() { return data + length; }
const char* begin() const { return data; }
const char* end() const { return data + length; }
const char* cbegin() const { return data; }
const char* cend() const { return data + length; }
const char* c_str() const
{
return data; // Already null-terminated
}
size_t size() const { return length; }
size_t max_size() const { return maxSize; }
};
What I want is to have a substr
function, which is simple enough:
[[nodiscard]] FixedString substr(size_type start, size_type end) const
{
if(end > length) {
end = length;
}
if(start >= end || start >= length)
{
return {};
}
else {
const size_type newLen = end - start;
FixedString copy;
copy.assignStr(&data[start], newLen);
return copy;
}
}
However, if I do this:
FixedString<16> myStr = "abcdef";
myStr = myStr.substr(1,4);
I am doing two memcpy
calls. I was wondering if there's a way to have an intermediate string_view
type (std::string_view
does not exist in my target platform stdlib). I would like it to have the following properties:
- When used as
auto x = somesStr.substr(x,y)
it should decay intoFixedString
copy of the original - If passed to the
FixedString
constructor or assignment operator, if the target and source are the same, memmove should be done instead
I am not sure if that's possible. I can of course just have a separate crop
non const function that does substr in place, but I was mostly curious if there's a way to hide this behavior in the substr
method.