I'm using a C routine to write to an std::string's datafield. This is a contrived example, in reality I'm getting a non-null terminated string and its size via network:
#include <string>
#include <cstdio>
#include <cstring>
const char* str = "Some examplory string..";
int main()
{
std::string some_str;
some_str.resize(strlen(str));
std::strcpy(&some_str.front(), str);
printf(some_str.c_str());
}
Now according to cppref there's no overload for resize() which just resizes and does not initialize memory with '\0' or something else. But why should I pay for that if all I'm doing is overwriting it again? Can this somehow be avoided?
For c++23, we have resize_and_overwrite
For old standard, let's hack it!
It works well on gcc5+, clang12+, msvc 1921+
// Our hacker function, resize a std::string without initializing memory
// (but still has '\0' in the end of string)
inline void resize(std::string& str, std::size_t sz);
#if __cpp_lib_string_resize_and_overwrite >= 202110L
inline void resize(std::string& str, std::size_t sz) {
str.resize_and_overwrite(sz, [](char*, std::size_t sz) {
return sz;
});
}
#elif (defined(__clang_major__) && __clang_major__ <= 11) ||(defined(_MSC_VER)&& _MSC_VER<=1920)
// old clang has bug in global friend function. discard it.
// old msvc don't support visit private, discard it.
inline void resize(std::string& str, std::size_t sz) { str.resize(sz); }
#else
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
template <typename Money_t, Money_t std::string::* p>
class string_thief {
public:
friend void string_set_length_hacker(std::string& bank, std::size_t sz) {
(bank.*p)(sz);
}
};
#elif defined(_MSVC_STL_VERSION)
template <typename Money_t, Money_t std::string::* p>
class string_thief {
public:
friend void string_set_length_hacker(std::string& bank, std::size_t sz) {
(bank.*p)._Myval2._Mysize = sz;
}
};
#endif
#if defined(__GLIBCXX__) // libstdc++
template class string_thief<void(std::string::size_type),
&std::string::_M_set_length>;
#elif defined(_LIBCPP_VERSION)
template class string_thief<void(std::string::size_type),
&std::string::__set_size>;
#elif defined(_MSVC_STL_VERSION)
template class string_thief<decltype(std::string::_Mypair),
&std::string::_Mypair>;
#endif
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \
defined(_MSVC_STL_VERSION)
void string_set_length_hacker(std::string& bank, std::size_t sz);
#endif
inline void resize(std::string& str, std::size_t sz) {
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \
defined(_MSVC_STL_VERSION)
str.reserve(sz);
string_set_length_hacker(str, sz);
str[sz] = '\0';
#else
str.resize(sz);
#endif
}
#endif
In C++23 you can use std::string::resize_and_overwrite. Example:
#include <cstring>
#include <iostream>
#include <string>
int main()
{
std::string str;
std::size_t capacity = 10; // Doesn't include the final `\0`, room for it is added automatically.
str.resize_and_overwrite(capacity, [](char *ptr, std::size_t count)
{
std::strcpy(ptr, "1234567890");
// Writing the terminating `\0` is optional, the character at `ptr[return_value]` is overwritten with `\0` anyway.
return count; // Doesn't include the final `\0`.
});
std::cout << str << '\n'; // 1234567890
}
It first allocates capacity bytes of storage, calls the lambda to fill it (the second parameter of the lambda redundantly receives the same capacity value).
The lambda then returns the final string size, which normally must match capacity, but can be less, if the lambda decides to write less bytes.
Cppreference claims that you must initialize the whole string in the lambda, otherwise the behavior is undefined. Whether you can get away with not doing it (to fill it at a later point) in practice is another question, I wouldn't risk it without a good reason.
I believe it's not guaranteed that capacity is actually used as the final string capacity (in practice it might be different for short SSO'd strings).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With