将窄字符输入流(char)重新解释为宽字符输入流(wchar_t)

9

我得到了一个包含UTF-16编码字符串的std::istream。想象一下,打开了一个UTF-16编码的文本文件,就像这样:

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

我想将这个流传递给一个需要std::wistream&参数的函数。我无法将文件流类型更改为std::wifstream。
问题:标准库或boost库中是否有任何设施可以让我“重新解释”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 );    

因为我喜欢重复利用现有的代码,而不是重复造轮子,所以我才会问这个问题。

如果您知道流是宽字符,为什么不一开始就创建wistream呢?或者您只想将原始流的一部分解码为宽字符流吗?(例如,是否存在标头指示内容是char还是wchar) - SergGr
我不知道该流是否为宽字符。我想编写一个函数,接受任何UTF-8 / UTF-16LE(BE作为奖励)编码的流,并通过读取BOM来决定如何解码它。 - zett42
顺便提一下,即使我一开始创建了wistream,也没有codecvt facet执行UTF-16 -> UTF-16转换(codecvt_utf16仅执行UTF-16 -> UCS-2)。 - zett42
2个回答

3
由于目前没有其他答案,我发布了我的解决方案,它使用了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"сайт вопросов и ответов для программистов";
}

2
这是正在进行中的工作。
这不是你应该使用的东西,但可能是一个提示,如果你还没有考虑过这样做的话,可以从这里开始。如果这并不有用,或者当你能够找到更好的解决方案时,我很乐意删除或扩展这个答案。
据我了解,你想读取一个UTF-8文件,并将每个单独的字符转换为wchar_t。
如果标准设施所做的太多了,你不能写自己的facet吗?
#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();
}

这不是你应该在代码中使用的东西。如果这条路行得通,你需要阅读文档,包括这个facet所需的内容,这些指针都表示什么以及你需要如何写入它们。

如果你想使用类似的东西,你需要考虑为facet使用哪些模板参数(如果有必要)。

更新 我现在已经更新了我的代码。输出函数现在更接近我们想要的了,我认为它还不够美观,只是一个测试代码,而且我仍然不确定为什么__from_next没有被更新(或保留)。

目前的问题是我们无法向流中写入数据。使用gcc时,我们只是从wbuffer_convert的同步中掉出来,而使用clang时,我们会收到SIGILL信号。


实际上,我想从字节流中读取UTF-16,而不是将UTF-8转换为wchar_t(这没有意义)。但编写一个codecvt可能是正确的方法,看起来比我的boost iostreams解决方案更少样板代码。 - zett42
1
好的,我现在添加了一个修改后的函数。这还在进行中,如果我们可以继续在这方面工作,我会很感兴趣,因为我认为这应该是可行的,但是在这方面的文档相当糟糕。 目前,在部分之后,一些字符在两个flush中都被复制了。请注意,reinterpret_cast可能太多了,但这是我们最后可以清理的。 - overseas
我接受这个答案(迟到总比不来得好),尽管我还没有找时间测试它。但是看起来这是正确的方法,因为它不需要额外的库(boost)并且似乎减少了总体样板代码量。 - zett42

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接