Skip to content

Rename CString->String and Update Implementation#202

Open
Mozz3d wants to merge 14 commits intowopss:masterfrom
Mozz3d:string-update
Open

Rename CString->String and Update Implementation#202
Mozz3d wants to merge 14 commits intowopss:masterfrom
Mozz3d:string-update

Conversation

@Mozz3d
Copy link
Contributor

@Mozz3d Mozz3d commented Mar 4, 2026

No description provided.

Mozz3d added 10 commits March 3, 2026 22:04
And outdated use of `CString` in the `function_registration/Main.cpp` example file
- Fix some more incorrect forward decls
- Remove a mostly unnecessary function that led to a circular dependency (something to resolve later)
- Manual casts to avoid warnings
Also moved impls to inl file to avoid circular dependency
idk why pre commit isnt working
Copy link
Owner

@wopss wopss left a comment

Choose a reason for hiding this comment

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

I took a quick look at the class API, will do more in-depth review later.

String(const std::string& aStr, const Memory::IAllocator* aAllocator = nullptr);
~String();

operator std::string() const;
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
operator std::string() const;
[[nodiscard]] operator std::string() const;

Comment on lines +49 to +51
operator std::string() const;

[[nodiscard]] operator std::string_view() const noexcept;
Copy link
Owner

Choose a reason for hiding this comment

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

I would put the conversion operators after the operator= or even at the end of all operators, but that's my preference, you can ignore it.

String(const String& aOther);
String(String&& aOther) noexcept;
String(const std::string& aStr, const Memory::IAllocator* aAllocator = nullptr);
~String();
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
~String();
~String();

Reference operator[](SizeType aIndex) noexcept;
ConstReference operator[](SizeType aIndex) const noexcept;

String& Assign(ConstPointer aStr, SizeType aSize);
Copy link
Owner

Choose a reason for hiding this comment

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

I would remove this, since StringView does the job here.


operator std::string() const;

[[nodiscard]] operator std::string_view() const noexcept;
Copy link
Owner

Choose a reason for hiding this comment

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

I don't remember if we spoke about this, but should [[nodiscard]] be on the same line or new line?

@psiberx

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't remember if we ever discussed it.

Copy link
Owner

Choose a reason for hiding this comment

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

What is your preference in this case? I would prefer it on a new line.

{
}

StringView(const String& aStr) noexcept;
Copy link
Owner

Choose a reason for hiding this comment

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

You are missing the move operations.

{
}

StringView(const String& aStr) noexcept;
Copy link
Owner

Choose a reason for hiding this comment

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

This can also be constexpr.

Comment on lines +48 to +49
bool operator==(const String& aRhs) const noexcept;
bool operator!=(const String& aRhs) const noexcept;
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
bool operator==(const String& aRhs) const noexcept;
bool operator!=(const String& aRhs) const noexcept;
constexpr bool operator==(const String& aRhs) const noexcept;
constexpr bool operator!=(const String& aRhs) const noexcept;

Comment on lines +81 to +84
constexpr operator bool() const noexcept
{
return !IsEmpty();
}
Copy link
Owner

Choose a reason for hiding this comment

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

Same comment as above about implicit conversions.

[[nodiscard]] constexpr bool Compare(StringView aOther) const noexcept
{
if (IsEmpty() && aOther.IsEmpty())
return true;
Copy link
Owner

Choose a reason for hiding this comment

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

Brackets!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What exactly is the rule on brackets? Evidently, I'm not clear on it

Copy link
Owner

Choose a reason for hiding this comment

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

Always use brackets.

Copy link
Contributor

Choose a reason for hiding this comment

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

Better for maintenance as scope is ready to add more statements, some logs when debugging, easier to read, etc.

String(ConstPointer aStr, const Memory::IAllocator* aAllocator = nullptr);
String(const String& aOther);
String(String&& aOther) noexcept;
String(const std::string& aStr, const Memory::IAllocator* aAllocator = nullptr);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should have one for string_view too.

Comment on lines +49 to +51
operator std::string() const;

[[nodiscard]] operator std::string_view() const noexcept;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Generally yes, I consider implicit conversions unsafe in the first place. But I can't think of any good examples for this case.

Comment on lines +131 to +134
[[nodiscard]] bool IsBackInternal() const noexcept;
[[nodiscard]] bool IsBackAllocated() const noexcept;
[[nodiscard]] bool IsBackViewing() const noexcept;
[[nodiscard]] bool IsBackExternal() const noexcept;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, these methods should be private (for internal use) of removed.

#pragma endregion
union Storage
{
enum class BackType : uint32_t
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps StorageMode is a better name.

Comment on lines +181 to +183
Internal = 0, // owned, inline
Allocated = 1, // owned, external
Viewing = 2 // unowned, external
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can continue to use Inline, same term is used for Variant class.
For other two I can suggest DynamicallyAllocated and ExternallyAllocated.

{
Internal = 0, // owned, inline
Allocated = 1, // owned, external
Viewing = 2 // unowned, external
Copy link
Collaborator

Choose a reason for hiding this comment

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

BTW, how to create externally allocated String right now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's all done in SetCapacity() (1744572317)
It also handles the transition from inline to allocated and vice versa

Copy link
Collaborator

@psiberx psiberx Mar 10, 2026

Choose a reason for hiding this comment

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

I don't get it. How it can transfer ownership in SetCapacity? This doesn't make sense.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I checked the function. It doesn't do anything if storage is set to external as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wdym? Like from viewing to allocated? It doesn't

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, my bad, I didn't understand what you were asking.
String can go from inline -> allocated and back, but it can't take ownership if viewing

Copy link
Owner

Choose a reason for hiding this comment

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

So when you said `owned, external you meant "allocated on heap, managed by this class"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

{
Internal = 0, // owned, inline
Allocated = 1, // owned, external
Viewing = 2 // unowned, external
Copy link
Owner

Choose a reason for hiding this comment

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

So when you said `owned, external you meant "allocated on heap, managed by this class"?

{
Internal = 0, // owned, inline
Allocated = 1, // owned, external
Viewing = 2 // unowned, external
Copy link
Owner

Choose a reason for hiding this comment

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

What is viewing? How can a string have an unowned string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When string is in that mode it's pretty much no different in functionality than StringView
String manages a ptr and size for the data, but does not own anything about the data

Copy link
Owner

Choose a reason for hiding this comment

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

That's really odd, is this even used in game? Maybe they share this with the StringView?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I looked into this much more in-depth, and it turns out my initial assumption wasn't necessarily right. This mode is used in the binary but only ever with stack-allocated scratch buffers. So it's more accurately a "scratch mode."
While the String still doesn't own the memory, it looks like it's always used as an in-place "API" over a piece of the stack for building.
I personally don't like this; it's dangerous and awkward, but it is what it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Such a use case really should be a dedicated type. IDK why they decided to cram it into the default String, but it's probably because they didn't have time to rebuild the string API

How do we want to do this?

Copy link
Owner

Choose a reason for hiding this comment

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

Would it be possible that they have a different class for this? Something like a nonmovable and noncopyable string.

Also, is there a pattern to that? Maybe they have more small string optimizations.

How do we want to do this?

We should just mention it here, but never allow for it to be used in the SDK.

Copy link
Collaborator

@psiberx psiberx Mar 17, 2026

Choose a reason for hiding this comment

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

I think the most dangerous part is mutability. We could allow it as const String only with a static helper function.
As for use cases, they seem to use it for const strings for their own interfaces that require String, e.g. "NULL" for RTTI/serialization.

Mozz3d added 4 commits March 14, 2026 06:08
String:
- Extract `String::Storage` union from within `String` and rename to `StringStorage`
- Extract `String::Storage::BackType` to standalone enum and rename to `EStringMode`
- Rename `EStringMode` fields to better reflect usage
- Fix allocator constness
- Remove uses of `ConstPointer, SizeType` methods in favor of `StringView`
- Add `std::string_view` constructor
- Removed implicit conversion operators
- Removed `CalcHash()`
- Removed unnecessary STL compliant definitions
- Removed mode check methods in favor of more explicit expressions
- Always use brackets

StringView:
- Document default move/copy constructors
- Make methods revolving `String` constexpr
- Always use brackets
I figured out pre-commit hook!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants