Home > Mobile >  What am I iterating in this find_if function?
What am I iterating in this find_if function?

Time:01-07

Here is my code:

bool isNotValid (char a) {
    if (isalpha(a) || a == '_')
    {
        cout << "\n- isalpha";
        return 0;
    }
    else
    {
        cout << "\n- notalpha";
        return 1;
    }
}

bool test123(const string& test)
{
    return find_if(test.begin(), test.end(), isNotValid) != test.end();
}

int main()
{
    string test;
    cout << "Test input: ";
    cin >> test;
    
    if (!test123(test))
        cout << "\n- Valid\n";
    else
        cout << "\n- Not Valid\n";
    return 0;
}

This is part of my code to check the validity of username in my program. I don't really understand what exactly I am iterating through when I insert the string into my function as address of the string. CPP reference states that find_if iterates from first to last position of a sequence.

Poked through the code with cout at different location, still didn't quite catch what is going on.

CodePudding user response:

You are iterating your string. You did not pass the address of the string. The function takes the string as a reference to const, meaning it passes the actual string (no copy is made) and the function is not allowed to modify the string. You are iterating character by character in your string and calling your function isNotValid() on each character.

Notes:

  • Instead of returning 1 or 0 from isNotValid(), return true or false.
  • Consider flipping your logic and renaming the function to isValid() instead. You would also have to change test123() to use std::find_if_not(). Finally, you would check if the returned iterator is end() and not if it's not.
  • But, if you do change isNotValid() to isValid(), you'd be better off switching from std::find_if() entirely to to std::all_of(). It makes more sense, is more readable, and returns a bool directly (No need to compare against end()).
  • But if you want to keep your function isNotValid(), the comment that suggests using std::any_of() is what I would recommend for the same reasons.

Here's my take on your code:

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

bool isValid(char a) {
  return std::isalpha(static_cast<unsigned char>(a)) || a == '_';  // !
}

bool test123(const std::string& test) {
  return std::all_of(test.begin(), test.end(), isValid);  // !
}

int main() {
  std::string testOne{"i_am_valid"};
  std::string testTwo{"i_am_invalid_123"};

  std::cout << "Testing: " << testOne << " : " << std::boolalpha
            << test123(testOne) << '\n';
  std::cout << "Testing: " << testTwo << " : " << std::boolalpha
            << test123(testTwo) << '\n';
}

Output:

❯ ./a.out 
Testing: i_am_valid : true
Testing: i_am_invalid_123 : false

I would argue that readability has stayed largely the same, but the mental load has been shifted; the Boolean flips make a bit more sense.

As you progress in your learning, you might not even want to have the function isValid() if it's a one-off thing. C 11 introduced lambdas, or functions as objects. C 20 also introduced ranges, so you don't have to pass a pair of iterators if you intend to iterate the whole container anyway.

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

bool test123(const std::string& test) {
  return std::ranges::all_of(test, [](const auto& c) {
           return std::isalpha(static_cast<unsigned char>(c)) || c == '_';
         });  // !
}

int main() {
  std::string testOne{"i_am_valid"};
  std::string testTwo{"i_am_invalid_123"};

  std::cout << "Testing: " << testOne << " : " << std::boolalpha
            << test123(testOne) << '\n';
  std::cout << "Testing: " << testTwo << " : " << std::boolalpha
            << test123(testTwo) << '\n';
}

That's a bit hairy to read if you're not familiar with lambdas, but I find lambdas useful for checks like this where you're just doing it the one time.

  •  Tags:  
  • c
  • Related