Random C++ compiler errors in Xcode 15.3 (15E204a)

After upgrading to Xcode 15.3 (15E204a) trivial C++ code of mine no longer compiles:

     const string text = textComponent->GetText();
     auto isEmpty = text.empty() || std::all_of(text.begin(), text.end(), std::isspace);

now yields compiler error "No matching function for call to 'all_of'" while working as expected on Version 15.2 (15C500b) and older.

Is there a regression in the latest Xcode update C++ support..?

Thanks,
Jay

Replies

FWIW the required includes - algorithm, string, ctype - are all #included, and as mentioned this compiles fine in all previous Xcode versions…

Consider this:

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

int main(int argc, const char * argv[]) {
    const std::string text = "Hello Cruel World!";
    
    auto isEmptyNG = std::all_of(text.begin(), text.end(), std::isspace);
                  // ^ No matching function for call to 'all_of'

    int (*myIsSpace)(int ch) = std::isspace;
    auto isEmptyOK = std::all_of(text.begin(), text.end(), myIsSpace);

    return 0;
}

Compiling with Xcode 15.3, the isEmptyNG example fails as you’ve described. OTOH the isEmptyOK works. AFAICT that’s because it ‘nails down’ the type of the predicate passed to all_of.

The interesting thing is that commenting out the include of <iostream> fixes the problem.

At that point we’ve run off the end of my C++ Fu. It seems that something in <iostream> is adding an overload on std::isspace that causes problems for the all_of template resolution, but I don’t have any great suggestions for how to proceed from there.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Interesting! Thanks for your insights.

I could fix the compiler error that starts popping up with Xcode 15.3 by switching to a lambda, i.e.

auto isEmpty = text.empty() || std::all_of(text.begin(), text.end(), [](const auto& c) { return std::isspace(c); });

though I can't see any reason why the original code should stop compiling under the latest compiler version...

Cheers,
Jay

First, do read the notes on the cppreference page for isspace:

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

Like all other functions from <cctype>, the behavior of std::isspace 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

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

As to why it doesn't work, consider this:

int foospace(int c)
{
  return c == ' ';
}

int foospace(char c)
{
  return c == ' ';
}

void foo()
{
  const std::string text = "Hello Cruel World!";
  auto is_empty = std::all_of(text.begin(), text.end(), foospace);
}

That fails, but remove either one of the overloads and it works.

std::isspace in <cctype> is supposed to take an int. I don't know where the other overload is coming from, nor what its argument type is. It's unfortunate that the compiler doesn't point out the overloads that it has considered. You might like to grep the headers!

So std::isspace is now the name of an overload-set, not a function. Sometimes that works, if the function signatures are sufficiently different, but clearly not in this case. The various ways to convert an overload-set to one of its members are described here:

https://en.cppreference.com/w/cpp/language/overloaded_address

One option is to assign to a function pointer of the correct type, which is what Quinn has done. Another is simply to cast:

  auto is_empty = std::all_of(text.begin(), text.end(), static_cast<int(*)(int)>(std::isspace));

But don't do that, because of the char vs. unsigned char issue mentioned at the start.