Loading...
Searching...
No Matches
TfEternalString Class Reference

An immutable, interned string whose character data is guaranteed to remain valid at a fixed address for the remainder of the process. More...

#include <eternalString.h>

Public Member Functions

TF_API TfEternalString ()
 Construct the empty eternal string.
 
std::string const & GetString () const
 Return the underlying string.
 
std::string_view GetStringView () const
 Return a view of the characters.
 
std::string-compatible accessors
char const * c_str () const
 
char const * data () const
 
size_t size () const
 
size_t length () const
 
bool empty () const
 
Implicit conversions

Both of these hand out a reference or a view of internal state, which for most types would be a dangling hazard.

Here they are safe even when the TfEternalString itself is a temporary, because the referent is immortal:

std::string const &name = TF_FUNC_NAME(); // safe
std::string_view view = TF_FUNC_NAME(); // safe
#define TF_FUNC_NAME()
Get the name of the current function as a TfEternalString.
Definition diagnostic.h:293

Note the std::string conversion yields a reference, not a value. That is what makes the durable-address promise survive being passed through an ordinary std::string const & parameter: no temporary is materialized, so a callee that retains c_str() is safe.

Providing both is a deliberate trade with one known cost. A callee overloaded on both std::string const & and std::string_view will fail to compile if passed a TfEternalString argument: either implicit conversion would be viable, so neither candidate is better and the call is ambiguous.

If you hit that, the local fix is to pass GetString() or GetStringView() to choose one explicitly. Overloading on both std::string const & and std::string_view is rare in practice, so providing both implicit conversions is judged worth the risk.

 operator std::string const & () const
 
 operator std::string_view () const
 

Static Public Member Functions

static TF_API TfEternalString Immortalize (std::string_view content)
 Return the TfEternalString naming content, interning its characters if they are not interned already.
 

Friends

Operators

These are hidden friends: findable only by argument-dependent lookup on TfEternalString, so they are not candidates for overload resolution in unrelated expressions.

They must be written out because the corresponding std:: operators are templates, and template argument deduction never considers user-defined conversions – so neither conversion above can rescue s + " x" or os << s.

std::string operator+ (TfEternalString a, std::string_view b)
 
std::string operator+ (std::string_view a, TfEternalString b)
 
std::string operator+ (TfEternalString a, TfEternalString b)
 
bool operator== (TfEternalString a, TfEternalString b)
 Compare by address, which interning makes a content identity in both directions – see the class documentation.
 
bool operator== (TfEternalString a, std::string_view b)
 
bool operator== (std::string_view a, TfEternalString b)
 
bool operator!= (TfEternalString a, TfEternalString b)
 
bool operator!= (TfEternalString a, std::string_view b)
 
bool operator!= (std::string_view a, TfEternalString b)
 
template<class HashState >
void TfHashAppend (HashState &h, TfEternalString s)
 Hash by address, which interning makes a content identity – see the class documentation.
 
TF_API friend std::ostream & operator<< (std::ostream &o, TfEternalString s)
 

Detailed Description

An immutable, interned string whose character data is guaranteed to remain valid at a fixed address for the remainder of the process.

The point of this type is not to be a better string; it is to make a promise to the functions you pass it to, in a form the compiler can check. A callee that receives a TfEternalString may:

  • retain c_str() / data() indefinitely without copying, and
  • use that address as a durable content identity.

The characters live in the TfToken table, which deduplicates by content, so the implication runs both ways and holds for the life of the process:

  • same address <==> same content

That is, two equal addresses denote the same characters, and two differing addresses denote different characters. Equality and hashing are therefore defined on the address, and are constant time regardless of length.

Interning also means there is only ever one copy of a given string. Two call sites that produce the same content share it, rather than each holding their own copy. TF_FUNC_NAME(), which returns a TfEternalString, does this whenever ArchGetPrettierFunctionName() truncates two names to the same text:

Outer::Member<int, float>() --> "Outer::Member"
Outer::Member<char, bool>() --> "Outer::Member"

Two call sites, one string, one address. Content that an ordinary TfToken elsewhere in the process already names is shared as well.

Cost

Creating one permanently retains an entry in the TfToken table. Nothing reclaims it, deliberately, to satisfy the immortality guarantee. Creating one also costs a hash, a strcmp, and a lock on one of the token table's sets, which is paid even when the content is already interned and no allocation results.

This makes the type suitable for a bounded set of strings fixed by the program source code – one per call site, as TF_FUNC_NAME() does – and actively not suitable for anything dynamic or derived from data. Never call Immortalize() in a loop, per prim, per frame, or on user input. Repeating the same content costs no additional space, since it dedups; the hazard is unbounded distinct content. If you cannot name a static bound on how many distinct strings your code creates, this is the wrong type.

Relationship to TfToken

This type is implemented on the TfToken table: Immortalize() creates an immortal token and keeps the address of its characters. That is where the content identity and the sharing described above come from.

Given that, the reason a separate type exists is not that an immortal TfToken is less durable. It is the same storage and exactly as durable. The reasons are these:

  • The promise would live in a runtime property rather than in the type. The durability is real and documented (see TfToken's immortal constructors) but it is a property of how a particular token was made, not of the type. A callee handed a TfToken must consult IsImmortal() and handle the case when it is not. Handed a TfEternalString, it knows statically that retaining the pointer is safe.
  • TfToken is not a drop-in for a string-like result. It has no c_str(), length(), or empty(), and no operator+ at all – and its implicit std::string const & conversion does not rescue concatenation, because std::operator+ is a template and deduction does not consider user-defined conversions. Every concatenating or c_str() call site would have to change.
  • tf/diagnostic.h, which defines TF_FUNC_NAME(), is deliberately light and does not include tf/token.h. Making it do so would pull the token table's dependencies into nearly every translation unit. Only tf/eternalString.cpp includes tf/token.h, so depending on the token table for storage costs this header nothing.

Definition at line 102 of file eternalString.h.

Constructor & Destructor Documentation

◆ TfEternalString()

TF_API TfEternalString ( )

Construct the empty eternal string.

Member Function Documentation

◆ c_str()

char const * c_str ( ) const
inline

Definition at line 126 of file eternalString.h.

◆ data()

char const * data ( ) const
inline

Definition at line 127 of file eternalString.h.

◆ empty()

bool empty ( ) const
inline

Definition at line 130 of file eternalString.h.

◆ GetString()

std::string const & GetString ( ) const
inline

Return the underlying string.

Definition at line 134 of file eternalString.h.

◆ GetStringView()

std::string_view GetStringView ( ) const
inline

Return a view of the characters.

Definition at line 137 of file eternalString.h.

◆ Immortalize()

static TF_API TfEternalString Immortalize ( std::string_view content)
static

Return the TfEternalString naming content, interning its characters if they are not interned already.

This is spelled as a named factory, not a converting constructor, so that callers make an explicit acknowledgement of intent at the call site. There is deliberately no implicit conversion from char const * or std::string for the same reason. See Cost.

Embedded NULs are not supported. content is trimmed at the first NUL, so Immortalize("a\\0b") and Immortalize("a") name the same string. This matches TfToken, which derives string identity from a NUL-terminated c-string and therefore cannot tell such a name from its prefix either. Consequently size() is always strlen(c_str()), and this type is for names, not for arbitrary binary content.

◆ length()

size_t length ( ) const
inline

Definition at line 129 of file eternalString.h.

◆ operator std::string const &()

operator std::string const & ( ) const
inline

Definition at line 166 of file eternalString.h.

◆ operator std::string_view()

operator std::string_view ( ) const
inline

Definition at line 167 of file eternalString.h.

◆ size()

size_t size ( ) const
inline

Definition at line 128 of file eternalString.h.

Friends And Related Symbol Documentation

◆ operator!= [1/3]

bool operator!= ( std::string_view a,
TfEternalString b )
friend

Definition at line 208 of file eternalString.h.

◆ operator!= [2/3]

bool operator!= ( TfEternalString a,
std::string_view b )
friend

Definition at line 205 of file eternalString.h.

◆ operator!= [3/3]

bool operator!= ( TfEternalString a,
TfEternalString b )
friend

Definition at line 202 of file eternalString.h.

◆ operator+ [1/3]

std::string operator+ ( std::string_view a,
TfEternalString b )
friend

Definition at line 183 of file eternalString.h.

◆ operator+ [2/3]

std::string operator+ ( TfEternalString a,
std::string_view b )
friend

Definition at line 180 of file eternalString.h.

◆ operator+ [3/3]

std::string operator+ ( TfEternalString a,
TfEternalString b )
friend

Definition at line 186 of file eternalString.h.

◆ operator== [1/3]

bool operator== ( std::string_view a,
TfEternalString b )
friend

Definition at line 199 of file eternalString.h.

◆ operator== [2/3]

bool operator== ( TfEternalString a,
std::string_view b )
friend

Definition at line 196 of file eternalString.h.

◆ operator== [3/3]

bool operator== ( TfEternalString a,
TfEternalString b )
friend

Compare by address, which interning makes a content identity in both directions – see the class documentation.

Constant time regardless of length.

Definition at line 193 of file eternalString.h.

◆ TfHashAppend

template<class HashState >
void TfHashAppend ( HashState & h,
TfEternalString s )
friend

Hash by address, which interning makes a content identity – see the class documentation.

Constant time regardless of length.

As with TfToken, which hashes its rep pointer for the same reason, this means hash values vary between runs and do not agree with the hash of an equal std::string. Do not persist them.

Definition at line 219 of file eternalString.h.


The documentation for this class was generated from the following file: