Last Update:
Six Handy Operations for String Processing in C++20/23
Table of Contents
In this article, we’ll explore six practical string processing operations introduced in C++20 and C++23. These features represent an evolution in this crucial area, covering a spectrum of operations from searching and appending to creation and stream handling.
Let’s start with a simple yet long-awaited feature…
1. contains(), C++23
Finally, after decades of standardization, we have a super easy way to check if there’s one string inside the other. No need to use .find(str) != npos!
Before C++23:
#include <string>
#include <iostream>
int main() {
const std::string url = "https://isocpp.org";
if (url.find("https") != std::string::npos &&
url.find(".org") != std::string::npos &&
url.find("isocpp") != std::string::npos)
std::cout << "You're using the correct site!\n";
}
And now, thanks to the proposal: P1679R3:
#include <string>
#include <iostream>
int main(){
const std::string url = "https://isocpp.org";
if (url.contains("https") &&
url.contains(".org") &&
url.contains("isocpp"))
std::cout << "you're using the correct site!\n";
}
Run at Compiler Explorer
As you can see, the new approach with contains() is more straightforward and intuitive.
2. starts_with(), ends_with(), C++20 & C++23
While contains() checks the whole string, in C++20, we got functions targeted for prefixes and suffixes. Have a look:
#include <string>
#include <iostream>
int main(){
const std::string url = "https://isocpp.org";
if (url.starts_with("https") && url.ends_with(".org"))
std::cout << "you're using the correct site!\n";
}
See at Compiler Explorer
It’s good to know that in C++23, those algorithms are also extended to ranges, so you’ll be able to write:
std::ranges::starts_with("Hello World", "Hello"sv)
std::vector nums { 1, 2, 3, 4};
std::ranges::ends_with(v, {3, 4});
The range versions are interesting, as they allow passing projections, and thus, we can be more flexible about the check between separate letters/elements.
And you can see more about the string functions in my separate article: starts_with() and ends_with() for Strings in C++20 - C++ Stories
3. Range operations, C++23
Thanks to ranges::to<>, many containers from the Standard Library got new ways to assign, append, or insert ranges.
The string class got the following members:
std::basic_string<CharT,Traits,Allocator>::insert_rangestd::basic_string<CharT,Traits,Allocator>::append_rangestd::basic_string<CharT,Traits,Allocator>::replace_with_range
For example (stealing a cool example from C++Reference):
#include <iostream>
#include <iterator>
#include <string>
int main() {
const auto source = {'l', 'i', 'b', '_'};
std::string target{"__cpp_containers_ranges"};
const auto pos = target.find("container");
auto iter = std::next(target.begin(), pos);
#ifdef __cpp_lib_containers_ranges
target.insert_range(iter, source);
#else
target.insert(iter, source.begin(), source.end());
#endif
std::cout << target;
}
Run at Compiler Explorere
See more in the paper P1206R7
We can also write a bit convoluted example with ranges::to:
#include <iostream>
#include <ranges>
#include <string>
int main() {
auto str = std::views::iota('a', 'g')
| std::views::transform([](auto const v){ return v + 2; })
| std::ranges::to<std::string>();
std::cout << str << '\n';
auto str2 = str | std::views::take(3) | std::ranges::to<std::string>();
std::cout << str2 << '\n';
}
The output:
cdefgh
cde
Run at Compiler Explorer
4. stringstream::view(), C++20
If you have to work with streams, and stringstream in particular, you may already know they might be pretty slow. Yet, in Modern C++, there are a couple of ways to speed things up a bit. For example, in C++20, you have the view() member function. This can be used as an alternative to str(). In short, rather than creating a copy of the internal string, you’ll get a view, so there is no need to dynamically allocate memory.
Have a look:
#include <iostream>
#include <sstream>
void* operator new(std::size_t sz){
std::cout << "Allocating: " << sz << '\n';
return std::malloc(sz);
}
int main() {
std::cout << "start...\n";
std::stringstream str;
str << 42;
str << " Hello C++20/23 Programming World";
std::cout << "print with str()\n";
std::cout << str.str() << '\n';
std::cout << "another try...\n";
std::cout << str.view() << '\n';
}
Possible output:
start...
Allocating: 513
print with str()
Allocating: 36
42 Hello C++20/23 Programming World
another try...
42 Hello C++20/23 Programming World
Run at Compile Explorer
When the example calls str.str(), you can see that there’s extra memory allocation for 36 bytes. But in the line with view(), there’s no allocation.
5. rvalue string constructor for stringstream, C++20
But there’s more to stringstream in C++20! For example, there’s an extra constructor that can take an rvalue reference to the string object, and thus it might not require an additional copy:
#include <iostream>
#include <sstream>
void* operator new(std::size_t sz){
std::cout << "Allocating: " << sz << '\n';
return std::malloc(sz);
}
int main() {
std::cout << "start...\n";
std::stringstream str { std::string("hello C++ programming World")};
}
When we compile the code with the C++17 mode (See at Compiler Explorer) you’ll see the following output:
start...
Allocating: 28
Allocating: 28
There’s clearly a copy of the string.
But try compiling that with the std=c++20 flag….
6. spanstream from C++23
If you need to use stringstream, and the previous techniques didn’t bring you any gains, and you want complete control over the internal memory, in C++23, you can easily update your code with spanstream!
In short, this is a stream class that operates on spans (also from C++20). Have a look at this simplistic example:
#include <iostream>
#include <sstream>
#include <spanstream> // << new headeer!
void* operator new(std::size_t sz){
std::cout << "Allocating: " << sz << '\n';
return std::malloc(sz);
}
int main() {
std::cout << "start...\n";
std::stringstream ss;
ss << "one string that doesn't fit into SSO";
ss << "another string that hopefully won't fit";
std::cout << "spanstream:\n";
char buffer[128] { 0 };
std::span<char> spanBuffer(buffer);
std::basic_spanstream<char> ss2(spanBuffer);
ss2 << "one string that doesn't fit into SSO";
ss2 << "another string that hopefully won't fit";
std::cout << buffer;
}
The output:
start...
Allocating: 513
spanstream:
one string that doesn't fit into SSOanother string that hopefully won't fit
In the first part - ss - the example shows a regular stringstream. Our simplistic memory allocation tracker points out 513 bytes had to be allocated.
On the other hand, in the second part, we have the new type, spanstream, which uses a preallocated buffer (a regular array), and this buffer is used as the internal memory for the stream object.
Conclusion
In this text, we explored a range of functionalities, from basic searching with contains() to sophisticated stream operations with spanstream, each bringing its unique advantage to the table. These enhancements can significantly simplify everyday programming tasks.
But there’s more in C++20/23:
- Prohibiting
std::basic_stringandstd::basic_string_viewconstruction fromnullptr- P2166 std::erasefor containers including strings- Better Unicode support, P1041R4
Back to you
- What string operations do you use the most often?
- Have you played with the latest additions to string/stream processing in C++20/23?
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
