Like the other overloads, we can insert the whole string within one
operation instead of doing a byte-by-byte append.
We only do byte-by-byte appending when padding is necessary.
It's undefined behavior to cast down to any other type and dereference
that pointer unless:
1. It's similar (*extremely* vague definition at face value, see below
for clarification)
2. The casted to type is either the signed/unsigned variant of the
original type. (e.g. it's fine to cast an int* to an unsigned int*
and vice-versa).
3. The casted to pointer type is either std::byte*, char*, or unsigned
char*.
With regards to type similarity, two types (X and Y) are considered
"similar" if:
1. They're the same type (naturally)
2. They're both pointers and the pointed-to types are similar (basically
1. but for pointers)
3. They're both pointers to members of the same class and the types of
the pointed-to members are similar in type.
4. They're both arrays of the same size or both arrays of unknown size
*and* the array element types are similar.
Plus, doing it this way doesn't do a byte-by-byte appending to the
underlying std::vector and instead allocates all the necessary memory up
front and slaps the elements at the end of it.
Previously this wasn't utilizing any of the compiler flags, meaning it
wasn't applying any of the specified warnings.
Since applying the warnings to the target, this uncovered a few warning
cases, such as shadowing class variables on MSVC, etc, which have been fixed.
While looping here does work fine, it's mildly inefficient, particularly
if the number of members being added is large, because it can result in
multiple allocations over the period of the insertion, depending on how
much extra memory push_back may allocate for successive elements.
Instead, we can just tell the std::vector that we want to slap the whole
contained sequence at the back of it with insert, which lets it allocate
the whole memory block in one attempt.
By taking the std::string by value in the constructor, this allows for
certain situations where copies can be elided entirely (when moving an
instance into the constructor)
e.g.
std::string var = ...
...
... = LiteralString(std::move(var)) // Or whatever other initialization
// is done.
No copy will be done in this case, the move transfers it into the
constructor, and then the move within the initializer list transfers it
into the member variable.
tl;dr: This allows the calling code to potentially construct less
std::string instances by allowing moving into the parameters themselves.