тл; д-р
Використовуйте бібліотеку ICU . Якщо ви цього не зробите, ваша конвертаційна процедура буде мовчки порушуватися на випадки, про які ви, мабуть, не знаєте навіть існуючих.
По- перше , ви повинні відповісти на питання: Що таке кодування з ваших std::string
? Це ISO-8859-1? А може, ISO-8859-8? Або кодова сторінка Windows 1252? Чи знає те, що ви використовуєте для перетворення верхніх у малі регістри? (Або це невдало провалюється для персонажів 0x7f
?)
Якщо ви використовуєте UTF-8 (єдиний розумний вибір серед 8-бітових кодувань) з std::string
якості контейнера, ви вже обманюєте себе, вважаючи, що ви все ще контролюєте речі, оскільки ви зберігаєте багатобайтову послідовність символів у контейнері що не знає багатобайтової концепції. Навіть щось таке просте, як .substr()
це тимбол. (Тому що розщеплення багатобайтової послідовності призведе до недійсного (під-) рядка.)
І як тільки ви спробуєте щось подібне std::toupper( 'ß' )
, у будь-якому кодуванні ви потрапляєте у глибокі проблеми. (Тому що це неможливо зробити "правильно" зі стандартною бібліотекою, яка може надати лише один результат результату, не "SS"
потрібний тут.) [1] Ще один приклад std::tolower( 'I' )
, який повинен давати різні результати залежно від локальної локації . У Німеччині 'i'
було б правильно; в Туреччині, 'ı'
(LATIN SMALL LETTER DOTLESS I) - очікуваний результат (який, знову ж таки, більше кодує один байт у кодуванні UTF-8). Ще один приклад - грецька сигма , великі '∑'
, малі регістри 'σ'
... за винятком кінця слова, де вона знаходиться'ς'
.
Отже, будь-яке перетворення випадків, яке працює на персонаж за раз, або, що ще гірше, байт за один раз, порушується дизайном.
Тоді існує точка , що стандартна бібліотека, для чого він це здатний робити, в залежності від локалі , які підтримуються на машині ваше програмне забезпечення працює на ... і що робити , якщо це не так ?
Тож те, що ви насправді шукаєте, це клас струн, який здатний правильно впоратися з усім цим, і це не будь-який з std::basic_string<>
варіантів .
(C ++ 11 Примітка: std::u16string
і std::u32string
це краще ., Але все ще не досконалі C ++ 20 принісstd::u8string
, але все це потрібно вказати на кодування. У багатьох інших аспектах вони все ще залишаються незнаними щодо механіки Unicode, як нормалізація, порівняння ... .)
Хоча Boost виглядає добре, API мудрий, Boost.Locale - це в основному обгортка навколо ICU . Якщо Boost компілюється з підтримкою ICU ... якщо це не так, Boost.Locale обмежений підтримкою локальної мови, зібраною для стандартної бібліотеки.
І повірте, отримувати підштовхування компілювати з ICU може бути реальною болю іноді. (Для Windows немає попередньо складених бінарних файлів, тому вам доведеться постачати їх разом зі своїм додатком, і це відкриє цілу нову банку черв'яків ...)
Тож особисто я рекомендував би отримати повну підтримку Unicode прямо з вуст коня та безпосередньо користуватися бібліотекою ICU :
#include <unicode/unistr.h>
#include <unicode/ustream.h>
#include <unicode/locid.h>
#include <iostream>
int main()
{
/* "Odysseus" */
char const * someString = u8"ΟΔΥΣΣΕΥΣ";
icu::UnicodeString someUString( someString, "UTF-8" );
// Setting the locale explicitly here for completeness.
// Usually you would use the user-specified system locale,
// which *does* make a difference (see ı vs. i above).
std::cout << someUString.toLower( "el_GR" ) << "\n";
std::cout << someUString.toUpper( "el_GR" ) << "\n";
return 0;
}
Компілюйте (із G ++ у цьому прикладі):
g++ -Wall example.cpp -licuuc -licuio
Це дає:
ὀδυσσεύς
Зауважте, що конверсія Σ <-> σ в середині слова, а конверсія Σ <-> ς в кінці слова. Жодне <algorithm>
рішення, яке не базується на цьому, не може вам цього дати.
[1] У 2017 році Рада з німецької ортографії вирішила, що "ẞ" U + 1E9E LATIN CAPITAL LETTER SHARP S може бути використаний офіційно, як варіант поруч із традиційним перетворенням "SS", щоб уникнути неоднозначності, наприклад, у паспортах (де імена з великої літери ). Мій гарний приклад, застарілий рішенням комітету ...