Как нормально использовать предикаты и функции из cctype в стандартных алгоритмах?

Решаю олимпиадную задачку в которой надо проанализировать регистр символов а потом их поменять. Написал следующее решение:

#include <algorithm>
#include <cctype>
...
std::string solve( std::string& str ) {
   size_t up_num = std::count_if( str.begin(), str.end(), std::isupper );
   size_t low_num = std::count_if( str.begin(), str.end(), std::islower );
   if ( up_num > low_num )
      std::for_each( str.begin(), str.end(), std::toupper );
   else
      std::for_each( str.begin(), str.end(), std::tolower );
   return str;
}

С компилятором Microsoft всё работает отлично, но LLVM и GCC отказываются это собирать. std::count_if не может принять std::isupper и std::islower в качестве предиката, а std::for_each не принимает std::toupper и std::tolower в качестве функтора. Если я правильно понял, это не из-за совпадения типов в сигнатуре функций cctype с тем, который выводят алгоритмы из итераторов. Как тут надо делать по нормальному, без использования промежуточных лямбд? Ведь навярняка, люди, которые это дизайнили, продумалил этот момент.


Ответы (2 шт):

Автор решения: user7860670

Эта проблема происходит так как у std::isupper и std::islower несколько перегрузок, одни объявлены в <cctype>, другие в <locale>. Видимо по-умолчанию в vc++ и стандартной библиотеке gcc эти заголовочные файлы по-разному включены в другие заголовочные файлы. Если в vc++ явно заинклюдить <locale>, то перестает собираться и там.

В таких случаях следует явно приводить предикат к указателю на нужную перегрузку:

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

void solve(::std::string & str)
{
    using t_Predicate = int ( * )(int);
    constexpr auto const p_up{static_cast<t_Predicate>(::std::isupper)};
    constexpr auto const p_low{static_cast<t_Predicate>(::std::islower)};
    auto const up_num{::std::count_if(str.begin(), str.end(), p_up)};
    auto const low_num{::std::count_if(str.begin(), str.end(), p_low)};
    if (low_num < up_num)
    {
        ::std::for_each(str.begin(), str.end(), p_up);
    }
    else
    {
        ::std::for_each(str.begin(), str.end(), p_low);
    }
    return;
}

online compiler

З.Ы. Предикаты из cctype вообще лучше не использовать, так как они могут неявно задействовать текущую локаль. Делайте лучше сначала один обычный цикл, подсчитывая количество букв в верхнем / нижнем регистре, а потом второй обычный цикл для преобразования регистра.

→ Ссылка
Автор решения: Fat-Zer

Ошибка возникает т.к. у std::isupper() есть дополнительный перегруженный вариант, приинимающий локаль, объявленый в <locale>. Можно явно привести тип:

std::count_if( str.begin(), str.end(), static_cast<int (*)(int)>(std::isupper));

А можно использовать Сишный ::isupper():

std::count_if( str.begin(), str.end(), ::isupper);

С остальными функциями всё аналогично.


std::for_each() работает не так как ты рассчитываешь: он не меняет исходную строку; здесь нужен std::transform():

std::transform( str.begin(), str.end(), str.begin(), ::toupper );

А если выпендрёж с функциональщиной вызван не религиозным рвением, то можно просто использовать обычный цикл:

for(char &c: str) {
  c = std::toupper((unsigned char)c);
}

А также, как напоминает @HolyBlackCat, isupper() и все прочие в принципе не стоит использовать с алгоритмами т.к. если char будет представлен знаковым типом, то при передаче в эти функции напрямую они дадут UB для отрицательных значений.

→ Ссылка