DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • DataWeave: Play With Dates (Part 1)
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • The Long Road to Java Virtual Threads
  • Exploring Exciting New Features in Java 17 With Examples

Trending

  • Integration Isn’t a Task — It’s an Architectural Discipline
  • AI-Driven Root Cause Analysis in SRE: Enhancing Incident Resolution
  • Designing a Java Connector for Software Integrations
  • Customer 360: Fraud Detection in Fintech With PySpark and ML
  1. DZone
  2. Data Engineering
  3. Data
  4. How to Check String or String View Prefixes and Suffixes in C++20

How to Check String or String View Prefixes and Suffixes in C++20

In this article, see how to check string or string view prefixes and suffixes in C++20.

By 
Bartłomiej Filipek user avatar
Bartłomiej Filipek
·
Updated Sep. 25, 20 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
11.7K Views

Join the DZone community and get the full member experience.

Join For Free

Up to (and including) C++17 if you wanted to check the start or the end in a string you have to use custom solutions, boost or other third-party libraries. Fortunately, this changes with C++20.

See the article where I'll show you the new functionalities and discuss a couple of examples.

This article was originally published at bfilipek.com.


Intro

Here's the main proposal that was added into C++20:

C++
 




x


 
1
std::string/std::string_view .starts_with() and .ends_with() P0457



In the new C++ Standard, we'll get the following member functions for std::string and std::string_view:

C++
 




xxxxxxxxxx
1


 
1
constexpr bool starts_with(string_view sv) const noexcept;
2
constexpr bool starts_with(CharT c ) const noexcept;
3
constexpr bool starts_with(const CharT* s ) const;


And also for suffix checking:

C++
 




xxxxxxxxxx
1


 
1
constexpr bool ends_with(string_view sv )const noexcept;
2
constexpr bool ends_with(CharT c ) const noexcept;
3
constexpr bool ends_with(const CharT* s ) const;



As you can see, they have three overloads: for a string_view, a single character and a string literal.

Simple example:

C++
 




xxxxxxxxxx
1


 
1
const std::string url { "https://isocpp.org" };
2
 
           
3
// string literals
4
if (url.starts_with("https") && url.ends_with(".org"))
5
    std::cout << "you're using the correct site!\n";
6
 
           
7
// a single char:
8
if (url.starts_with('h') && url.ends_with('g'))
9
    std::cout << "letters matched!\n";



You can play with this basic example @Wandbox

Token Processing Example

Below, you can find an example which takes a set of HTML tokens and extracts only the text that would be rendered on that page. It skips the HTML tags and leaves only the content and also tries to preserve the line endings.

C++
 




xxxxxxxxxx
1
44


 
1
#include <algorithm>
2
#include <iostream>
3
#include <iterator>
4
#include <string>
5
#include <vector>
6
 
           
7
int main() {
8
    const std::vector<std::string> tokens { 
9
        "<header>",
10
        "<h1>",
11
        "Hello World",
12
        "</h1>",
13
        "<p>",
14
        "This is my super cool new web site.",
15
        "</p>",
16
        "<p>",
17
        "Have a look and try!",
18
        "</p>",
19
        "</header>"
20
    };
21
 
           
22
    const auto convertToEol = [](const std::string& s) {
23
        if (s.starts_with("</h") || s.starts_with("</p"))
24
            return std::string("\n");
25
 
           
26
        return s;
27
    };
28
 
           
29
    std::vector<std::string> tokensTemp;
30
    std::transform(tokens.cbegin(), tokens.cend(),            
31
                   std::back_inserter(tokensTemp),
32
                   convertToEol);
33
 
           
34
    const auto isHtmlToken = [](const std::string& s) {
35
        return s.starts_with('<') && s.ends_with('>');
36
    };
37
 
           
38
    std::erase_if(tokensTemp, isHtmlToken); // cpp20!
39
 
           
40
    for (const auto& str : tokensTemp)
41
        std::cout << str;
42
 
           
43
    return 0;
44
}


You can play with the code at @Wandbox

The most interesting parts:

  • there's a lambda convertToEol which takes a stringand then returns the same string or converts that to EOL if it detects the closing HTML tag.
    • the lambda is then used in the std::transform call that converts the initial set of tokens into the temporary version.
  • later the temporary tokens are removed from the vector by using another predicate lambda. This time we have a simple text for an HTML token.
  • you can also see the use of std::erase_if which works nicely on our vector, this functionality is also new to C++20. There's no need to use remove/erase pattern.
  • at the end we can display the final tokens that are left

Prefix and a (Sorted) Container

Let's try another use case. For example, if you have a container of strings, then you might want to search for all elements that start with a prefix.

A simple example with unsorted vector:

C++
 




xxxxxxxxxx
1
29


 
1
#include <algorithm>
2
#include <iostream>
3
#include <iterator>
4
#include <string>
5
#include <string_view>
6
#include <vector>
7
 
           
8
int main() {
9
    const std::vector<std::string> names { "Edith", "Soraya", "Nenita",
10
        "Lanny", "Marina", "Clarine", "Cinda", "Mike", "Valentin",
11
        "Sylvester", "Lois", "Yoshie", "Trinidad", "Wilton", "Horace",
12
        "Willie", "Aleshia", "Erminia", "Maybelle", "Brittany", "Breanne"
13
        "Kerri", "Dakota", "Roseanna", "Edra", "Estell", "Fabian"
14
        "Arlen", "Madeleine", "Genia" }; // listofrandomnames.com
15
 
           
16
    const std::string_view prefix { "M" };
17
    const std::vector<std::string> foundNames = [&names, &prefix]{
18
        std::vector<std::string> tmp;
19
        std::copy_if(names.begin(), names.end(),
20
              std::back_inserter(tmp), [&prefix](const std::string& str){
21
                  return str.starts_with(prefix);
22
              });
23
        return tmp;
24
    }();
25
 
           
26
    std::cout << "Names starting with \"" << prefix << "\":\n";
27
    for (const auto& str : foundNames)
28
        std::cout << str << ", ";
29
}






xxxxxxxxxx
1
29


 
1
#include <algorithm>
2
#include <iostream>
3
#include <iterator>
4
#include <string>
5
#include <string_view>
6
#include <vector>
7
 
           
8
int main() {
9
    const std::vector<std::string> names { "Edith", "Soraya", "Nenita",
10
        "Lanny", "Marina", "Clarine", "Cinda", "Mike", "Valentin",
11
        "Sylvester", "Lois", "Yoshie", "Trinidad", "Wilton", "Horace",
12
        "Willie", "Aleshia", "Erminia", "Maybelle", "Brittany", "Breanne"
13
        "Kerri", "Dakota", "Roseanna", "Edra", "Estell", "Fabian"
14
        "Arlen", "Madeleine", "Genia" }; // listofrandomnames.com
15
 
           
16
    const std::string_view prefix { "M" };
17
    const std::vector<std::string> foundNames = [&names, &prefix]{
18
        std::vector<std::string> tmp;
19
        std::copy_if(names.begin(), names.end(),
20
              std::back_inserter(tmp), [&prefix](const std::string& str){
21
                  return str.starts_with(prefix);
22
              });
23
        return tmp;
24
    }();
25
 
           
26
    std::cout << "Names starting with \"" << prefix << "\":\n";
27
    for (const auto& str : foundNames)
28
        std::cout << str << ", ";
29
}


Play with code @Wandbox

In the sample code, I'm computing the foundNames vector, which contains entries from names that starts with a given prefix. The code uses copy_if with a predicated that leverages the starts_wth() function.

On the other hand, if you want to have better complexity for this kind of queries, then it might be wiser to store those strings (or string views) in a sorted container. This happens when you have a std::map, std::set, or you sort your container. Then, we can use lower_bound to quickly (logarithmically) find the first element that should match the prefix and then perform a linear search for neighbour elements.

C++
 




xxxxxxxxxx
1
15


 
1
#include <algorithm>
2
#include <iostream>
3
#include <iterator>
4
#include <string>
5
#include <string_view>
6
#include <vector>
7
#include <set>
8
 
           
9
int main() {
10
    const std::set<std::string> names { "Edith", "Soraya", "Nenita",
11
        "Lanny", "Marina", "Clarine", "Cinda", "Mike", "Valentin",
12
        "Sylvester", "Lois", "Yoshie", "Trinidad", "Wilton", "Horace",
13
        "Willie", "Aleshia", "Erminia", "Maybelle", "Brittany", "Breanne"
14
        "Kerri", "Dakota", "Roseanna", "Edra", "Estell", "Fabian"
15
        "Arlen", "Madeleine", "Genia", "Mile", "Ala", "Edd" }; 



C++
 




xxxxxxxxxx
1
20


 
1
         // listofrandomnames.com
2
 
           
3
    const std::string prefix { "Ed" };
4
    const auto startIt = names.lower_bound(prefix);
5
 
           
6
    const std::vector<std::string> foundNames = [&names, &startIt, &prefix]{
7
        std::vector<std::string> tmp;
8
        for (auto it = startIt; it != names.end(); ++it)
9
            if ((*it).starts_with(prefix))
10
                tmp.emplace_back(*it);
11
            else
12
                break;
13
 
           
14
        return tmp;
15
    }();
16
 
           
17
    std::cout << "Names starting with \"" << prefix << "\":\n";
18
    for (const auto& str : foundNames)
19
        std::cout << str << ", ";
20
}


Play with the code @Wandbox

As a side note, you might also try a different approach which should be even faster. Rather than checking elements one by one starting from the lower bound iterator, we can also modify the last letter of the pattern in that way that it's "later" in the order. And then you can also find lower_bound from that modified pattern. Then you have two ranges and better complexity (two log(n) searchers). I'll leave that experiment for you as a "homework".

Case (in)Sensitivity

All examples that I've shown so far used regular std::string objects and thus we could only compare strings case-sensitively. But what if you want to compare it case-insensitive?

For example, in boost there are separate functions that do the job:

In QT, similar functions take an additional argument that selects the comparison technique ( QString Class - starts_with).

In the Standard Library, we can do another way... and write your trait for the string object.

As you can recall std::string is just a specialisation of the following template:

C++
 




xxxxxxxxxx
1


 
1
template<class charT, 
2
         class traits = char_traits<charT>,
3
         class Allocator = allocator<charT>>
4
class basic_string;



The traits class is used for all core operations that you can perform on characters. You can implement a trait that compares strings case-insensitively.

You can find the examples in the following websites:

After implementing the trait you'll end up with a string type that is different than std::string:

C++
 




xxxxxxxxxx
1


 
1
using istring = std::basic_string<char, case_insensitive_trait>;
2
// assuming case_insensitive_trait is a proper char trait



Is that a limitation? For example, you won't be able to easily copy from std::string into your new istring. For some designs, it might be fine, but on the other hand, it can also be handy to have just a simple runtime parameter or a separate function that checks case-insensitively. What's your opinion on that?

Another option is to "normalise" the string and the pattern - for example, make it lowercase. This approach, unfortunately, requires to create extra copies of the strings, so might not be the best.

Sorry for a little interruption in the flow :)
I've prepared a little bonus if you're interested in Modern C++, check it out here.

Compiler Support

Most of the recent compiler vendors already support the new functionality!

Summary

In this article, you've seen how to leverage new functionality that we get with C++20: string prefix and suffix checking member functions.

You've seen a few examples, and we also discussed options if you want your comparisons to be case insensitive.

And you can read about other techniques of prefix and suffix checking in:

  • How to Check If a String Is a Prefix of Another One in C++ - Fluent C++
  • C++ : Check if a String starts with an another given String – thispointer.com

More from the Author:

Image title

Bartek recently published a book - "C++17 In Detail"- learn the new C++ Standard in an efficient and practical way. The book contains more than 360 pages filled with C++17 content!

Strings Data Types

Published at DZone with permission of Bartłomiej Filipek, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • DataWeave: Play With Dates (Part 1)
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • The Long Road to Java Virtual Threads
  • Exploring Exciting New Features in Java 17 With Examples

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!