Переинтерпретируйте узкий (char) входной поток как широкий (wchar_t) поток

Мне дали std::istream, который содержит строку в кодировке UTF-16. Представьте себе текстовый файл в кодировке UTF-16, который был открыт следующим образом:

std::ifstream file( "mytext_utf16.txt", std::ios::binary );

Я хочу передать этот поток функции, которая принимает параметр std::wistream&. Я не могу изменить тип файлового потока на std::wifstream.

Вопрос. Есть ли в стандартной или расширенной библиотеках какие-либо средства, которые позволяют мне интерпретировать istream как wistream?

Я представляю себе класс адаптера, аналогичный std::wbuffer_convert, за исключением того, что он должен не делать никакого преобразования кодировки. По сути, для каждого wchar_t, который считывается из класса адаптера, он должен просто прочитать два байта из связанного istream и reinterpret_cast их в wchar_t.

Я создал реализацию с использованием boost::iostreams, которую можно используется так и работает как шарм:

std::ifstream file( "mytext_utf16.txt", std::ios::binary );

// Create an instance of my adapter class.
reinterpret_as_wide_stream< std::ifstream > wfile( &file );

// Read a wstring from file, using the adapter.
std::wstring str;
std::get_line( wfile, str );    

Почему я тогда спрашиваю? Потому что мне нравится повторно использовать существующий код, а не изобретать велосипед.


person zett42    schedule 19.02.2017    source источник
comment
Если вы знаете, что поток широкоформатный, почему бы не создать wistream в первую очередь? Или вы хотите декодировать только часть исходного потока как широкоформатный? (Например, есть заголовок, в котором указано, является ли содержимое char или wchar)   -  person SergGr    schedule 27.02.2017
comment
Я не знаю, что поток широкоформатный. Я хочу написать функцию, которая принимает любой кодированный поток UTF-8/UTF-16LE (BE в качестве бонуса) и решает, как его декодировать, читая спецификацию.   -  person zett42    schedule 27.02.2017
comment
В качестве примечания, даже если бы я создал wistream в первую очередь, нет аспекта codecvt, который выполняет преобразование UTF-16 → UTF-16 (codecvt_utf16 выполняет только UTF-16 → UCS-2).   -  person zett42    schedule 27.02.2017


Ответы (2)


Это работа в процессе

Это не то, что вы должны использовать, но, вероятно, подсказка, с чего вы можете начать, если вы еще не думали о том, чтобы сделать такую ​​​​вещь. Если это бесполезно или когда вы можете найти лучшее решение, я рад удалить или расширить этот ответ.

Насколько я понимаю, вы хотите прочитать файл UTF-8 и просто перевести каждый символ в wchar_t.

Если это слишком много, что делают стандартные средства, не могли бы вы написать свой собственный аспект.

#include <codecvt>
#include <locale>
#include <fstream>
#include <cwchar>
#include <iostream>
#include <fstream>

class MyConvert
{
 public:
  using state_type = std::mbstate_t;
  using result = std::codecvt_base::result;
  using From = char;
  using To = wchar_t;
  bool always_noconv() const throw() {
    return false;
  }
  result in(state_type& __state, const From* __from,
    const From* __from_end, const From*& __from_next,
    To* __to, To* __to_end, To*& __to_next) const
  {
    while (__from_next != __from_end) {
      *__to_next = static_cast<To>(*__from_next);
      ++__to_next;
      ++__from_next;
    }
    return result::ok;
  }
  result out(state_type& __state, const To* __from,
      const To* __from_end, const To*& __from_next,
      From* __to, From* __to_end, From*& __to_next) const
  {
    while (__from_next < __from_end) {
      std::cout << __from << " " << __from_next << " " << __from_end << " " << (void*)__to << 
        " " << (void*)__to_next << " " << (void*)__to_end << std::endl;
      if (__to_next >= __to_end) {
        std::cout << "partial" << std::endl;
        std::cout << "__from_next = " << __from_next << " to_next = " <<(void*) __to_next << std::endl;
        return result::partial;
      }
      To* tmp = reinterpret_cast<To*>(__to_next);
      *tmp = *__from_next;
      ++tmp;
      ++__from_next;
      __to_next = reinterpret_cast<From*>(tmp);
    }
    return result::ok;
  }
};

int main() {
  std::ofstream of2("test2.out");
  std::wbuffer_convert<MyConvert, wchar_t> conv(of2.rdbuf());
  std::wostream wof2(&conv);
  wof2 << L"сайт вопросов и ответов для программистов";
  wof2.flush();
  wof2.flush();
}

Это не то, что вы должны использовать в своем коде. Если все идет в правильном направлении, вам нужно прочитать документацию, в том числе, что нужно для этого аспекта, что означают все эти указатели и как вам нужно писать в них.

Если вы хотите использовать что-то подобное, вам нужно подумать о том, какие аргументы шаблона вы должны использовать для фасета (если они есть).

Обновить Я обновил свой код. Я думаю, что функция out теперь ближе к тому, что мы хотим. Это не красиво и просто тестовый код, и я до сих пор не уверен, почему __from_next не обновляется (или не сохраняется).

В настоящее время проблема в том, что мы не можем писать в поток. С gcc мы просто выпадаем из синхронизации wbuffer_convert, для clang получаем SIGILL.

person overseas    schedule 27.02.2017
comment
На самом деле я хочу читать UTF-16 из потока байтов, а не приводить UTF-8 к wchar_t (что не имеет смысла). Но написание codecvt, вероятно, является правильным способом, и похоже, что он будет менее шаблонным, чем мое решение boost iostreams. - person zett42; 28.02.2017
comment
Хорошо, теперь я добавил модифицированную функцию. Это все еще продолжается, мне было бы интересно, если бы мы могли продолжить работу над этим, поскольку я думаю, что это должно быть выполнимо, но документация в этом аспекте довольно плохая. В настоящее время некоторые символы (после партиала) дублируются в обоих флешах. Обратите внимание, что reinterpret_casts может быть слишком много, но в конце концов мы можем это исправить. - person overseas; 01.03.2017
comment
Я принимаю этот ответ (лучше поздно, чем никогда), хотя пока не нашел времени его проверить. Но это похоже на правильный путь, поскольку он не требует дополнительной библиотеки (ускорения) и выглядит менее шаблонным. - person zett42; 17.03.2017

Поскольку других ответов пока нет, я публикую свое решение, использующее библиотеку Boost.Iostreams. Хотя это довольно просто, я все же думаю, что должно быть более простое решение.

Сначала мы создаем класс шаблона, который моделирует Boost.Iostreams устройство и служит адаптером для связанного узкого устройства. Он перенаправляет операции чтения, записи и поиска на соответствующее устройство, но корректирует значения положения и размера потока, чтобы учесть разницу в размере между узкие и широкие типы символов.

"basic_reinterpret_device.h"

#pragma once
#include <boost/iostreams/traits.hpp>
#include <boost/iostreams/read.hpp>
#include <boost/iostreams/write.hpp>
#include <boost/iostreams/seek.hpp>

// CategoryT: boost.iostreams device category tag
// DeviceT  : type of associated narrow device
// CharT    : (wide) character type of this device adapter 
template< typename CategoryT, typename DeviceT, typename CharT >
class basic_reinterpret_device
{
public:
    using category = CategoryT;               // required by boost::iostreams device concept
    using char_type = CharT;                  // required by boost::iostreams device concept
    using associated_device = DeviceT;
    using associated_char_type = typename boost::iostreams::char_type_of< DeviceT >::type;
    static_assert( sizeof( associated_char_type ) == 1, "Associated device must have a byte-sized char_type" );

    // Default constructor.
    basic_reinterpret_device() = default;

    // Construct from a narrow device
    explicit basic_reinterpret_device( DeviceT* pDevice ) :
        m_pDevice( pDevice ) {}

    // Get the asociated device.
    DeviceT* get_device() const { return m_pDevice; }

    // Read up to n characters from the underlying data source into the buffer s, 
    // returning the number of characters read; return -1 to indicate EOF
    std::streamsize read( char_type* s, std::streamsize n )
    {
        ThrowIfDeviceNull();

        std::streamsize bytesRead = boost::iostreams::read( 
            *m_pDevice, 
            reinterpret_cast<associated_char_type*>( s ), 
            n * sizeof( char_type ) );

        if( bytesRead == static_cast<std::streamsize>( -1 ) )  // EOF
            return bytesRead;
        return bytesRead / sizeof( char_type );
    }

    // Write up to n characters from the buffer s to the output sequence, returning the 
    // number of characters written.
    std::streamsize write( const char_type* s, std::streamsize n )
    {
        ThrowIfDeviceNull();

        std::streamsize bytesWritten = boost::iostreams::write(
            *m_pDevice, 
            reinterpret_cast<const associated_char_type*>( s ), 
            n * sizeof( char_type ) );

        return bytesWritten / sizeof( char_type );
    }

    // Advances the read/write head by off characters, returning the new position, 
    // where the offset is calculated from:
    //  - the start of the sequence if way == ios_base::beg
    //  - the current position if way == ios_base::cur
    //  - the end of the sequence if way == ios_base::end
    std::streampos seek( std::streamoff off, std::ios_base::seekdir way ) 
    {
        ThrowIfDeviceNull();

        std::streampos newPos = boost::iostreams::seek( *m_pDevice, off * sizeof( char_type ), way );
        return newPos / sizeof( char_type );
    }

protected:
    void ThrowIfDeviceNull()
    {
        if( ! m_pDevice )
            throw std::runtime_error( "basic_reinterpret_device - no associated device" );
    }

private:
    DeviceT* m_pDevice = nullptr;
};

Чтобы упростить использование этого шаблона, мы создаем несколько шаблонов псевдонимов для наиболее распространенных тегов устройств Boost.Iostreams. На их основе мы создаем шаблоны псевдонимов для создания совместимых со стандартом потоковых буферов и потоков.

"reinterpret_stream.h"

#pragma once
#include "basic_reinterpret_device.h"

#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/traits.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>

struct reinterpret_device_tag : virtual boost::iostreams::source_tag, virtual boost::iostreams::sink_tag {};
struct reinterpret_source_seekable_tag : boost::iostreams::device_tag, boost::iostreams::input_seekable {};
struct reinterpret_sink_seekable_tag : boost::iostreams::device_tag, boost::iostreams::output_seekable {};


template< typename DeviceT, typename CharT >
using reinterpret_source = basic_reinterpret_device< boost::iostreams::source_tag, DeviceT, CharT >;

template< typename DeviceT, typename CharT >
using reinterpret_sink = basic_reinterpret_device< boost::iostreams::sink_tag, DeviceT, CharT >;

template< typename DeviceT, typename CharT >
using reinterpret_device = basic_reinterpret_device< reinterpret_device_tag, DeviceT, CharT >;

template< typename DeviceT, typename CharT >
using reinterpret_device_seekable = basic_reinterpret_device< boost::iostreams::seekable_device_tag, DeviceT, CharT >;

template< typename DeviceT, typename CharT >
using reinterpret_source_seekable = 
    basic_reinterpret_device< reinterpret_source_seekable_tag, DeviceT, CharT >;

template< typename DeviceT, typename CharT >
using reinterpret_sink_seekable = 
    basic_reinterpret_device< reinterpret_sink_seekable_tag, DeviceT, CharT >;


template< typename DeviceT >
using reinterpret_as_wistreambuf = boost::iostreams::stream_buffer< reinterpret_source_seekable< DeviceT, wchar_t > >;

template< typename DeviceT >
using reinterpret_as_wostreambuf = boost::iostreams::stream_buffer< reinterpret_sink_seekable< DeviceT, wchar_t > >;

template< typename DeviceT >
using reinterpret_as_wstreambuf = boost::iostreams::stream_buffer< reinterpret_device_seekable< DeviceT, wchar_t > >;


template< typename DeviceT >
using reinterpret_as_wistream = boost::iostreams::stream< reinterpret_source_seekable< DeviceT, wchar_t > >;

template< typename DeviceT >
using reinterpret_as_wostream = boost::iostreams::stream< reinterpret_sink_seekable< DeviceT, wchar_t > >;

template< typename DeviceT >
using reinterpret_as_wstream = boost::iostreams::stream< reinterpret_device_seekable< DeviceT, wchar_t > >;

Примеры использования:

#include "reinterpret_stream.h"

void read_something_as_utf16( std::istream& input )
{
    reinterpret_as_wistream< std::istream > winput( &input );
    std::wstring wstr;
    std::getline( winput, wstr );   
}

void write_something_as_utf16( std::ostream& output )
{
    reinterpret_as_wostream< std::ostream > woutput( &output );
    woutput << L"сайт вопросов и ответов для программистов";
}
person zett42    schedule 21.02.2017