Как нормально использовать предикаты и функции из 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 шт):
Эта проблема происходит так как у 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;
}
З.Ы. Предикаты из cctype вообще лучше не использовать, так как они могут неявно задействовать текущую локаль. Делайте лучше сначала один обычный цикл, подсчитывая количество букв в верхнем / нижнем регистре, а потом второй обычный цикл для преобразования регистра.
Ошибка возникает т.к. у 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 для отрицательных значений.