I am a little late to this game (and will explain why later), but I often find that students need to identify if a string contains either a single character or a sub-string. Which, in most languages, is pretty easy to identify as they provide a contains() function on strings which returns a boolean to determine if the string contains the character or sub-string. However, C++ does not provide such a function (yet), and to use Boost to get this functionality is a bit excessive. But is it easy to do in C++? Well, based on the solutions I have seen from students, the answer is no! and the main reason for this is that they do not know modern C++ or, more specifically, std::string.

Before I show how I would address this issue, I will show some solutions to this that students have shown me. One solution is to find a single character, and one is to find a sub-string, with both being based on for-loops. For finding a single character, the students would iterate the string to identify for each index if the element at that index match the character and return true if it does.

bool contains(const std::string& str, const char sub)
{
for (size_t i = 0; i < str.size(); ++i)
{
if (str.at(i) == sub)
{
return true;
}
}
return false;
}



This solution is not bad or incorrect. But a question I often ask the students is; “If you do not need the index, why use”. Next, I will suggest that if they insist on using loops, then they should use a for-each-loop as it reduces the risk of index errors. This morphs the code above to the one below. This solution is essentially no different than the students’ solution, and it is just “safer” to use. But we will make it much better.

bool contains(const std::string& str, const char sub)
{
for (const auto& elm : str)
{
if (elm == sub)
{
return true;
}
}
return false;
}


Now for identifying if a string contains a sub-string, it often looks similar to this code below. This code has the same risk of indexing errors if we are not careful, but it is also much more challenging to change this from indexed based loops to for-each. Additionally, we have to remember to break the loop and return if we find the information we are looking for, all in all. Not nice compared to, for instance, Python, where we simply can call contains.

bool contains (const std::string& str, const std::string& sub)
{
for (size_t i = 0; i < str.size(); ++i)
{
if (str.at(i) == sub.at(0))
{
bool found = true;
for (size_t j = 0; j < sub.size(); ++j)
{
if (str.at(i + j) != sub.at(j))
{
found = false;
break;
}
}
if (found)
{
return found;
}
}
}
return false;
}


But how do we make this code simpler and safer to use? Well let us take a look on basic_string what we will see is that string has a function called find which returns a size_type which can be compared to std::string:npos. A cool thing here is that find works with both a char and string input, so instead of having different functions for contains, we can easily define a single function.

#include <string>

template<typename T>
bool cotains(const std::string& str, const T sub)
{
return str.find(sub) != std::string::npos;
}


The benefit of this solution is that it completely removes the need for loops (loops exposed to us) and indexing. Additionally, it is super easy to read. If you want to test the function compile this code:

#include <string>

#include <iostream>

template<typename T>
bool contains(const std::string& str, const T elm)
{
return str.find(sub) != std::string::npos;
}

int main(void)
{

std::string base = "abba";
std::string sub = "ba";
bool _contains = contains(base, sub);
std::cout << _contains << "\n";

sub = "da";
_contains = contains(base, sub);
std::cout << _contains << "\n";

char csub = 'a';
_contains = contains(base, csub);
std::cout << _contains << "\n";

csub = 'f';
_contains = contains(base, csub);
std::cout << _contains << "\n";
}


Now, this is the best solution I know of in C++11/14/17/20. But remember that I wrote C++ does not have a contains function yet? Well, with C++23, this will change as the contains` function will be added with the new standard, and if you follow the link, you will see that what we have implemented above is the same as what is intended to be used in C++23.

./Lars