Repository: godotengine/godot-cpp (C++)

Issue:
`CharString` implementation doesn't match Godot's
### Godot version

v4.1.beta.custom_build.19c1d3c7a

### godot-cpp version

4.1.dev (1bc9ca7)

### System information

Linux

### Issue description

The `CharString` implementation in godot-cpp is much simpler, and not compatible with what's in Godot. This makes it harder to have a module that can also be compiled as a GDExtension.

This was discovered when trying to compile the `text_server_adv` module in Godot as a GDExtension.

See: https://github.com/godotengine/godot/pull/77532

### Steps to reproduce

```bash
# Assuming you have Godot's source code in 'godot'
cd godot/modules/text_server_adv/gdextension_build
scons
```

### Minimal reproduction project

n/a



Fix (patch):
diff --git a/include/godot_cpp/templates/cowdata.hpp b/include/godot_cpp/templates/cowdata.hpp
index 1753687e1..18320d802 100644
--- a/include/godot_cpp/templates/cowdata.hpp
+++ b/include/godot_cpp/templates/cowdata.hpp
@@ -32,13 +32,13 @@
 #define GODOT_COWDATA_HPP
 
 #include <godot_cpp/classes/global_constants.hpp>
-#include <godot_cpp/core/class_db.hpp>
 #include <godot_cpp/core/error_macros.hpp>
 #include <godot_cpp/core/math.hpp>
 #include <godot_cpp/core/memory.hpp>
 #include <godot_cpp/templates/safe_refcount.hpp>
 
 #include <cstring>
+#include <new>
 
 namespace godot {
 
@@ -48,6 +48,9 @@ class Vector;
 template <class T, class V>
 class VMap;
 
+template <class T>
+class CharStringT;
+
 // Silence a false positive warning (see GH-52119).
 #if defined(__GNUC__) && !defined(__clang__)
 #pragma GCC diagnostic push
@@ -62,6 +65,9 @@ class CowData {
 	template <class TV, class VV>
 	friend class VMap;
 
+	template <class TS>
+	friend class CharStringT;
+
 private:
 	mutable T *_ptr = nullptr;
 
diff --git a/include/godot_cpp/variant/char_string.hpp b/include/godot_cpp/variant/char_string.hpp
index fa58bbdf4..f8f51696c 100644
--- a/include/godot_cpp/variant/char_string.hpp
+++ b/include/godot_cpp/variant/char_string.hpp
@@ -31,83 +31,100 @@
 #ifndef GODOT_CHAR_STRING_HPP
 #define GODOT_CHAR_STRING_HPP
 
+#include <godot_cpp/templates/cowdata.hpp>
+
 #include <cstddef>
 #include <cstdint>
 
 namespace godot {
 
-class CharString {
-	friend class String;
-
-	const char *_data = nullptr;
-	int _length = 0;
-
-	CharString(const char *str, int length);
-
-public:
-	int length() const;
-	const char *get_data() const;
-
-	CharString(CharString &&p_str);
-	void operator=(CharString &&p_str);
-	CharString() {}
-	~CharString();
-};
+template <class T>
+class CharStringT;
 
-class Char16String {
-	friend class String;
+template <class T>
+class CharProxy {
+	template <class TS>
+	friend class CharStringT;
 
-	const char16_t *_data = nullptr;
-	int _length = 0;
+	const int _index;
+	CowData<T> &_cowdata;
+	static inline const T _null = 0;
 
-	Char16String(const char16_t *str, int length);
+	_FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &p_cowdata) :
+			_index(p_index),
+			_cowdata(p_cowdata) {}
 
 public:
-	int length() const;
-	const char16_t *get_data() const;
-
-	Char16String(Char16String &&p_str);
-	void operator=(Char16String &&p_str);
-	Char16String() {}
-	~Char16String();
-};
+	_FORCE_INLINE_ CharProxy(const CharProxy<T> &p_other) :
+			_index(p_other._index),
+			_cowdata(p_other._cowdata) {}
 
-class Char32String {
-	friend class String;
+	_FORCE_INLINE_ operator T() const {
+		if (unlikely(_index == _cowdata.size())) {
+			return _null;
+		}
 
-	const char32_t *_data = nullptr;
-	int _length = 0;
+		return _cowdata.get(_index);
+	}
 
-	Char32String(const char32_t *str, int length);
+	_FORCE_INLINE_ const T *operator&() const {
+		return _cowdata.ptr() + _index;
+	}
 
-public:
-	int length() const;
-	const char32_t *get_data() const;
+	_FORCE_INLINE_ void operator=(const T &p_other) const {
+		_cowdata.set(_index, p_other);
+	}
 
-	Char32String(Char32String &&p_str);
-	void operator=(Char32String &&p_str);
-	Char32String() {}
-	~Char32String();
+	_FORCE_INLINE_ void operator=(const CharProxy<T> &p_other) const {
+		_cowdata.set(_index, p_other.operator T());
+	}
 };
 
-class CharWideString {
+template <class T>
+class CharStringT {
 	friend class String;
 
-	const wchar_t *_data = nullptr;
-	int _length = 0;
-
-	CharWideString(const wchar_t *str, int length);
+	CowData<T> _cowdata;
+	static inline const T _null = 0;
 
 public:
-	int length() const;
-	const wchar_t *get_data() const;
-
-	CharWideString(CharWideString &&p_str);
-	void operator=(CharWideString &&p_str);
-	CharWideString() {}
-	~CharWideString();
+	_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
+	_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
+	_FORCE_INLINE_ int size() const { return _cowdata.size(); }
+	Error resize(int p_size) { return _cowdata.resize(p_size); }
+
+	_FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); }
+	_FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
+	_FORCE_INLINE_ const T &operator[](int p_index) const {
+		if (unlikely(p_index == _cowdata.size())) {
+			return _null;
+		}
+
+		return _cowdata.get(p_index);
+	}
+	_FORCE_INLINE_ CharProxy<T> operator[](int p_index) { return CharProxy<T>(p_index, _cowdata); }
+
+	_FORCE_INLINE_ CharStringT() {}
+	_FORCE_INLINE_ CharStringT(const CharStringT<T> &p_str) { _cowdata._ref(p_str._cowdata); }
+	_FORCE_INLINE_ void operator=(const CharStringT<T> &p_str) { _cowdata._ref(p_str._cowdata); }
+	_FORCE_INLINE_ CharStringT(const T *p_cstr) { copy_from(p_cstr); }
+
+	void operator=(const T *p_cstr);
+	bool operator<(const CharStringT<T> &p_right) const;
+	CharStringT<T> &operator+=(T p_char);
+	int length() const { return size() ? size() - 1 : 0; }
+	const T *get_data() const;
+	operator const T *() const { return get_data(); };
+
+protected:
+	void copy_from(const T *p_cstr);
 };
 
+typedef CharStringT<char> CharString;
+typedef CharStringT<char16_t> Char16String;
+typedef CharStringT<char32_t> Char32String;
+typedef CharStringT<wchar_t> CharWideString;
+
 } // namespace godot
 
 #endif // GODOT_CHAR_STRING_HPP
diff --git a/src/variant/char_string.cpp b/src/variant/char_string.cpp
index 0c8cd0fb0..856037c43 100644
--- a/src/variant/char_string.cpp
+++ b/src/variant/char_string.cpp
@@ -38,116 +38,120 @@
 #include <godot_cpp/godot.hpp>
 
 #include <cmath>
+#include <string>
 
 namespace godot {
 
-int CharString::length() const {
-	return _length;
-}
-
-const char *CharString::get_data() const {
-	return _data;
-}
-
-CharString::CharString(CharString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+template <typename L, typename R>
+_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) {
+	while (true) {
+		const char32_t l = *l_ptr;
+		const char32_t r = *r_ptr;
+
+		if (l == 0 && r == 0) {
+			return false;
+		} else if (l == 0) {
+			return true;
+		} else if (r == 0) {
+			return false;
+		} else if (l < r) {
+			return true;
+		} else if (l > r) {
+			return false;
+		}
 
-void CharString::operator=(CharString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+		l_ptr++;
+		r_ptr++;
+	}
 }
 
-CharString::CharString(const char *str, int length) :
-		_data(str), _length(length) {}
-
-CharString::~CharString() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
+template <class T>
+bool CharStringT<T>::operator<(const CharStringT<T> &p_right) const {
+	if (length() == 0) {
+		return p_right.length() != 0;
 	}
-}
 
-int Char16String::length() const {
-	return _length;
+	return is_str_less(get_data(), p_right.get_data());
 }
 
-const char16_t *Char16String::get_data() const {
-	return _data;
-}
+template <class T>
+CharStringT<T> &CharStringT<T>::operator+=(T p_char) {
+	const int lhs_len = length();
+	resize(lhs_len + 2);
 
-Char16String::Char16String(Char16String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+	T *dst = ptrw();
+	dst[lhs_len] = p_char;
+	dst[lhs_len + 1] = 0;
 
-void Char16String::operator=(Char16String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+	return *this;
 }
 
-Char16String::Char16String(const char16_t *str, int length) :
-		_data(str), _length(length) {}
+template <class T>
+void CharStringT<T>::operator=(const T *p_cstr) {
+	copy_from(p_cstr);
+}
 
-Char16String::~Char16String() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
+template <>
+const char *CharStringT<char>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return "";
 	}
 }
 
-int Char32String::length() const {
-	return _length;
+template <>
+const char16_t *CharStringT<char16_t>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return u"";
+	}
 }
 
-const char32_t *Char32String::get_data() const {
-	return _data;
+template <>
+const char32_t *CharStringT<char32_t>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return U"";
+	}
 }
 
-Char32String::Char32String(Char32String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+template <>
+const wchar_t *CharStringT<wchar_t>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return L"";
+	}
 }
 
-void Char32String::operator=(Char32String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+template <class T>
+void CharStringT<T>::copy_from(const T *p_cstr) {
+	if (!p_cstr) {
+		resize(0);
+		return;
+	}
 
-Char32String::Char32String(const char32_t *str, int length) :
-		_data(str), _length(length) {}
+	size_t len = std::char_traits<T>::length(p_cstr);
 
-Char32String::~Char32String() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
+	if (len == 0) {
+		resize(0);
+		return;
 	}
-}
 
-int CharWideString::length() const {
-	return _length;
-}
+	Error err = resize(++len); // include terminating null char
 
-const wchar_t *CharWideString::get_data() const {
-	return _data;
-}
-
-CharWideString::CharWideString(CharWideString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+	ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string.");
 
-void CharWideString::operator=(CharWideString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+	memcpy(ptrw(), p_cstr, len);
 }
 
-CharWideString::CharWideString(const wchar_t *str, int length) :
-		_data(str), _length(length) {}
-
-CharWideString::~CharWideString() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
-	}
-}
+template class CharStringT<char>;
+template class CharStringT<char16_t>;
+template class CharStringT<char32_t>;
+template class CharStringT<wchar_t>;
 
 // Custom String functions that are not part of bound API.
 // It's easier to have them written in C++ directly than in a Python script that generates them.
@@ -228,56 +232,61 @@ String rtoss(double p_val) {
 CharString String::utf8() const {
 	int length = internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char *cstr = memnew_arr(char, size);
-	internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), cstr, length);
+	CharString str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return CharString(cstr, length);
+	return str;
 }
 
 CharString String::ascii() const {
 	int length = internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char *cstr = memnew_arr(char, size);
-	internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), cstr, length);
+	CharString str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return CharString(cstr, length);
+	return str;
 }
 
 Char16String String::utf16() const {
 	int length = internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char16_t *cstr = memnew_arr(char16_t, size);
-	internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), cstr, length);
+	Char16String str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return Char16String(cstr, length);
+	return str;
 }
 
 Char32String String::utf32() const {
 	int length = internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char32_t *cstr = memnew_arr(char32_t, size);
-	internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), cstr, length);
+	Char32String str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return Char32String(cstr, length);
+	return str;
 }
 
 CharWideString String::wide_string() const {
 	int length = internal::gdextension_interface_string_to_wide_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	wchar_t *cstr = memnew_arr(wchar_t, size);
-	internal::gdextension_interface_string_to_wide_chars(_native_ptr(), cstr, length);
+	CharWideString str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_wide_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return CharWideString(cstr, length);
+	return str;
 }
 
 String &String::operator=(const char *p_str) {