2015-07-07 1 views
3

У меня есть итератор char - a std::istreambuf_iterator<char>, завернутый в пару адаптеров - уступающий UTF-8 байтам. Я хочу прочитать один символ UTF-32 (a char32_t). Могу ли я сделать это с помощью STL? Как?UTF-8 в UTF-32 на итераторах с использованием STL

Есть std::codecvt_utf8<char32_t>, но это, видимо, работает только на char*, а не на итераторах.

Вот упрощенная версия моего кода:

#include <iostream> 
#include <sstream> 
#include <iterator> 

// in the real code some boost adaptors etc. are involved 
// but the important point is: we're dealing with a char iterator. 
typedef std::istreambuf_iterator<char> iterator; 

char32_t read_code_point(iterator& it, const iterator& end) 
{ 
    // how do I do this conversion? 
    // codecvt_utf8<char32_t>::in() only works on char* 
    return U'\0'; 
} 

int main() 
{ 
    // actual code uses std::istream so it works on strings, files etc. 
    // but that's irrelevant for the question 
    std::stringstream stream(u8"\u00FF"); 
    iterator it(stream); 
    iterator end; 
    char32_t c = read_code_point(it, end); 
    std::cout << std::boolalpha << (c == U'\u00FF') << std::endl; 
    return 0; 
} 

Я знаю, что есть библиотека Boost.regex итератор для этого, но я хотел бы избежать подталкивания библиотек, которые не только заголовочные и это чувствует как будто STL должен быть способен.

+3

Не используйте тег [tag: stl], если вы действительно не имеете в виду STL, то есть библиотеку с 1990-х годов, из которой возникли большие части стандартной библиотеки C++. –

+0

О, я вижу. Спасибо за подсказку. –

+0

@JonathanWakely Общее использование, тег wiki, короткая форма тега и полезность термина (кто, собственно, говорит об оригинальной STL больше?) Вроде не согласен с вами там. Я имею в виду, что быть педантичным - это весело, но SO - это нечто большее, чем забава. – Yakk

ответ

3

Я не думаю, что вы можете сделать это напрямую с помощью codecvt_utf8 или любых других стандартных компонентов библиотеки. Чтобы использовать codecvt_utf8, вам нужно будет скопировать байты из потока итератора в буфер и преобразовать буфер.

Что-то, как это должно работать:

char32_t read_code_point(iterator& it, const iterator& end) 
{ 
    char32_t result; 
    char32_t* resend = &result + 1; 
    char32_t* resnext = &result; 
    char buf[7]; // room for 3-byte UTF-8 BOM and a 4-byte UTF-8 character 
    char* bufpos = buf; 
    const char* const bufend = std::end(buf); 
    std::codecvt_utf8<char32_t> cvt; 
    while (bufpos != bufend && it != end) 
    { 
    *bufpos++ = *it++; 
    std::mbstate_t st{}; 
    const char* be = bufpos; 
    const char* bn = buf; 
    auto conv = cvt.in(st, buf, be, bn, &result, resend, resnext); 
    if (conv == std::codecvt_base::error) 
     throw std::runtime_error("Invalid UTF-8 sequence"); 
    if (conv == std::codecvt_base::ok && bn == be) 
     return result; 
    // otherwise read another byte and try again 
    } 
    if (it == end) 
    throw std::runtime_error("Incomplete UTF-8 sequence"); 
    throw std::runtime_error("No character read from first seven bytes"); 
} 

Это, кажется, делать больше работы, чем это необходимо, повторно сканировать всю последовательность UTF-8 в [buf, bufpos) на каждой итерации (и делает вызов виртуальной функции в codecvt_utf8::do_in) , Теоретически реализация codecvt_utf8::in может считывать неполную многобайтовую последовательность и сохранять информацию о состоянии в аргументе mbstate_t, так что следующий вызов возобновится с того места, где последний остался, только потребляя новые байты, а не перерабатывая неполную многобайтную последовательность, которая была уже видел.

Однако реализации не требуется использовать mbstate_t аргумент для сохранения состояния между вызовами и на практике, по крайней мере одной реализации codecvt_utf8::in (тот, который я написал для GCC) не использовать его вообще. Из моих экспериментов кажется, что реализация libC++ также не использует его. Это означает, что они прекращают преобразование перед неполной многобайтовой последовательностью и оставляют указатель from_next (здесь аргумент bn), указывающий на начало этой неполной последовательности, так что следующий вызов должен начинаться с этой позиции и (надеюсь) предоставить достаточные дополнительные байты для завершения последовательности и разрешить чтение и преобразование полного символа Юникода в char32_t. Поскольку вы только пытаетесь прочитать один код, это означает, что он вообще не конвертирует, потому что остановка перед неполной многобайтовой последовательностью означает остановку в первом байте.

Вполне возможно, что некоторые реализации сделать использовать mbstate_t аргумент, так что вы можете изменить функцию выше, чтобы справиться с этим делом, как хорошо, но, чтобы быть портативным он все равно нужно, чтобы справиться с реализациями, которые игнорируют mbstate_t. Поддержка обоих типов реализации значительно усложнила бы эту функцию, поэтому я оставил ее простой и написал форму, которая должна работать со всеми реализациями, даже если они действительно используют mbstate_t. Поскольку вы только собираетесь считывать до 7 байт за раз (в худшем случае ... средний случай может быть только одним или двумя байтами, в зависимости от входного текста), стоимость повторного сканирования первых нескольких байтов каждый раз не должно быть огромным.

Чтобы получить лучшую производительность от codecvt_utf8, вам следует избегать одновременного преобразования одного кода, поскольку он предназначен для преобразования массивов символов, а не отдельных. Так как вам всегда нужно копировать буфер char, вы можете скопировать большие куски из последовательности ввода итератора и преобразовать целые куски.Это уменьшило бы вероятность обнаружения неполных многобайтовых последовательностей, поскольку только последние 1-3 байта в конце фрагмента нужно было бы переработать, если фрагмент закончится в неполной последовательности, все предыдущее в куске было бы преобразовано ,

Чтобы получить лучшую производительность при чтении отдельных кодовых точек, вы должны, вероятно, полностью избегать codecvt_utf8 и либо свернуть свой собственный (если вам нужно только UTF-8 для UTF-32BE, это не так сложно) или использовать стороннюю библиотеку, такую ​​как ICU.

+0

Да, это должно сработать ... Мне это не очень нравится, но это дает мне представление; Я немного экспериментирую. –

+0

Немного icky - O (n^2) в количестве байтов, из которых состоит символ UTF-32, и n вызывает код cvt. Учитывая, насколько сложным является вышеупомянутое, почти разумно сворачивать свои собственные. (ik) – Yakk

+0

Нет, никогда. Я надеялся, что могу вызвать 'codecvt_utf8 :: in()' один символ за раз, сохраняя 'mbstate_t' inbetween, но это, похоже, не работает. –

Смежные вопросы