Search code examples
c++std

Differences between `isdigit` and `std::isdigit`


Here's the code to find whether a string contains only digits:

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

int main() {
    std::string s("123");
    std::all_of(s.begin(), s.end(), std::isdigit);  //error!
}

But the code doesn't compile, with the following errors (produced using clang++):

error: no matching function for call to 'all_of'
note: candidate template ignored: couldn't infer template argument '_Predicate'
all_of(_InputIterator __first, _InputIterator __last, _Predicate __pred)

When I replace std::isdigit with isdigit, the code compiles without errors.

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

int main() {
    std::string s("123");
    std::all_of(s.begin(), s.end(), isdigit);  //OK
}

I tried the above on both Apple clang and linux g++, and got similar errors.

I know that isdigit is from the C header ctype.h, and that std::isdigit is a wrapper defined in C++ header cctype, so essentially they should be the same. So what are the differences between isdigit and std::isdigit?


Solution

  • In C++, you are not allowed to take the address of most standard library functions:

    https://en.cppreference.com/w/cpp/language/extending_std#Addressing_restriction

    The behavior of a C++ program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer, reference (for free functions and static member functions) or pointer-to-member (for non-static member functions) to a standard library function or an instantiation of a standard library function template, unless it is designated an addressable function

    std::isdigit() is not an addressable standard library function, so you can't use it directly as an algorithm predicate.

    That begin said, even if you could pass std::isdigit() directly as a predicate, doing so would risk undefined behavior:

    https://en.cppreference.com/w/cpp/string/byte/isdigit

    Like all other functions from <cctype>, the behavior of std::isdigit is undefined if the argument's value is neither representable as unsigned char nor equal to EOF. To use these functions safely with plain chars (or signed chars), the argument should first be converted to unsigned char:

    bool my_isdigit(char ch)
    {
        return std::isdigit(static_cast<unsigned char>(ch));
    }
    

    Similarly, they should not be directly used with standard algorithms when the iterator's value type is char or signed char. Instead, convert the value to unsigned char first:

    int count_digits(const std::string& s)
    {
        return std::count_if(s.begin(), s.end(), 
                          // static_cast<int(*)(int)>(std::isdigit)         // wrong
                          // [](int c){ return std::isdigit(c); }           // wrong
                          // [](char c){ return std::isdigit(c); }          // wrong
                             [](unsigned char c){ return std::isdigit(c); } // correct
                            );
    }
    

    As such, since you need to convert each char to unsigned char and back, you should call std::isdigit() inside of a lambda that is used as the predicate for std::all_of(), eg:

    std::all_of(s.begin(), s.end(),
      [](unsigned char ch){ return std::isdigit(ch); }
    );