constexpr шаблонный cctype

Виктор Губин
Виктор Губин

Пртотоип может выглядеть например вот так:

#include <climits>
#include <type_traits>

#if (__cplusplus >= 201703L) && defined(__cpp_char8_t)
#  define HAS_CHAR8_T 1
#endif

namespace std {

static constexpr const unsigned int BITS = sizeof(unsigned int) * CHAR_BIT;
static constexpr const unsigned int ALBT_SIZE = 26;
static constexpr const unsigned int LAT_A = static_cast<unsigned int>('a');
static constexpr const unsigned int LAT_A_C = static_cast<unsigned int>('A');
static constexpr const unsigned int DEL_CHAR = 0x7f;
static constexpr const unsigned int ACCENT_CHAR = static_cast<unsigned int>('^');
static constexpr const unsigned int EM_CHAR = static_cast<unsigned int>('!');
static constexpr const unsigned int LL_CHAR = static_cast<unsigned int>('_');
static constexpr const unsigned int ZERRO = static_cast<unsigned int>('0');
static constexpr const unsigned int SPACE = static_cast<unsigned int>(' ');
static constexpr const unsigned int TAB = static_cast<unsigned int>('\t');
static constexpr const unsigned int WHITE_SPACES_COUNT = 5;
static constexpr const char UPPER_LOWER_MASK = 0x5F;

template<typename> struct __is_char_type_helper : public false_type {};
template<> struct __is_char_type_helper<char> : public true_type {};
template<> struct __is_char_type_helper<wchar_t> : public true_type {};
template<> struct __is_char_type_helper<char16_t> : public true_type {};
template<> struct __is_char_type_helper<char32_t> : public true_type {};

#ifdef HAS_CHAR8_T
template<> struct __is_char_type_helper<char8_t> : public true_type {};
#endif

template<typename _Tp>
struct is_char_type : public __is_char_type_helper<typename remove_cv<_Tp>::type>::type
{};

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type >
constexpr bool is_ascii(const char_t ch) noexcept
{
	return 0 == ( static_cast<unsigned int>(ch) & (~ DEL_CHAR) );
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_alpha(const char_t ch) noexcept
{
	return ( (static_cast<unsigned int>(ch) | BITS ) - LAT_A) < ALBT_SIZE;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_digit(const char_t ch) noexcept 
{
	return (static_cast<unsigned int>(ch)-ZERRO) < 10;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_alnum(const char_t ch) noexcept
{
	return is_alpha(ch) || is_digit(ch);
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_space(const char_t ch) noexcept 
{
	return SPACE == static_cast<unsigned int>(ch) || (static_cast<unsigned int>(ch) - TAB) < WHITE_SPACES_COUNT;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_lower(const char_t ch) noexcept 
{
    return (static_cast<unsigned int>(ch)-LAT_A) < ALBT_SIZE;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_upper(const char_t ch) noexcept 
{
    return (static_cast<unsigned int>(ch)-LAT_A_C) < ALBT_SIZE;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr char_t to_lower(const char_t ch) noexcept 
{
    return is_upper( ch ) ? static_cast<char_t>(static_cast<unsigned char>(ch) | BITS) : ch;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr char_t to_upper(const char_t ch) noexcept 
{
    return is_lower(ch) ? (ch & UPPER_LOWER_MASK) : ch;
}

template<typename char_t> 
constexpr bool is_cntrl(const char_t ch) noexcept
{
    return static_cast<unsigned int>(ch) < SPACE || ch == DEL_CHAR;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_blank(const char_t ch) noexcept
{
	return static_cast<unsigned int>(ch) == SPACE || static_cast<unsigned int>(ch) == TAB;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_graph(const char_t ch) noexcept
{
	return (static_cast<unsigned int>(ch)-EM_CHAR) < ACCENT_CHAR;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_print(const char_t ch) noexcept
{
	return  (static_cast<unsigned int>(ch)-SPACE) < LL_CHAR;
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_punct(const char_t ch) noexcept
{
	return is_graph(ch) && !is_alnum(ch);
}

template<typename char_t, class = typename std::enable_if<std::is_char_type<char_t>::value>::type>
constexpr bool is_xdigit(const char_t ch)
{
	return is_digit(ch) || ((static_cast<unsigned int>(ch)|BITS)-LAT_A) < 6;
}

} // namespace std

Немного тестов c разными компиляторами

7
рейтинг
6 комментариев
Antervis

Погоди, ты хочешь ввести новое семейство функций в snake_case вместо существующих clibcase методов? Дерзко
 
По коду: раз уж в с++20 едут концепты, лучше сразу на них писать. А лишние static и const стоит убрать. И unsigned int для символьных констант не нужен.

Antervis
Виктор Губин

Antervis, кейсы не принципиальны, врочем как и header name и будут-ли constexpr функции подменять оригинальные или дополонять их.

Сompile time ctype нужен на случай если мы допустим хотим проверить что в строке вроде "https://stdcpp.ru/proposals/c61961b5-65e1-4a70-98aa-6fa67de8deb4" нет пробелов, естесвенно если строка - литерал, то в compile time-ме.

2. unsigned int нужен в прототипе, (char-ы - все знаковые, пока ряду компиляторов ключами не сообщили обратное). Берем скажем isdigit:

(c - '0') < 10

если с - заковый берем -  - /* '\0' */ 0 - 48  =  -48; -48 < 10 .  C unsigned,  0xFFFF FFD0 > 0xA.

Альтернтатива:

c >= '0' && c <= '9'

при выключеной оптимизации (на дебаге) разница вот.

Виктор Губин
Antervis

Виктор Губин, зато с включенной оптимизацией gcc и msvc справляются чуть лучше. Я бы сделал через make_unsigned

Antervis
Виктор Губин

Antervis, можно и так  (после чего все простится быть включенным внутрь char_traits).  MS VC++ сразу отказался со всем этим жить.

Виктор Губин
Обновлено 
webreh

Меня определенно напрягает реализация этих методов для случаев char8_t/char16_t/char32_t. Не очень приятно иметь функции, умеющие проверять половину кодпоинта да еще и отвечающую false на попытку спросить мнение, является ли символ U+FF10 (full width digit 0) собственно digit. 

webreh
Обновлено 
Виктор Губин

webreh, оригинальный isdigit  и не должен понимать U+FF10 впрочем как и что либо кроме 0123456789 U+0030-U+0039 т.е. в приделах значений UNICODE Latin 1. Если выйти в полный диапазон UNICODE -  то не понятно что делать с: Римскими, Сингальскими, Румми, Персидскими (Фарси) [٠١٢٣٤/۴٥/۵٦/۶٧۸٩] и прочими цыфрами. Логически  - это все числа.

Виктор Губин
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения