使用 c++2b 编码不可知的解析

Encoding agnostic parsing with c++2b

有时我必须解析各种编码的文本文件, 我想知道即将推出的标准是否会为此带来一些工具 因为我对我目前的解决方案不是很满意。 但是,我什至不确定这是否是正确的方法 我定义了一个仿函数模板来从流中提取一个字符:

#include <string>
#include <istream> // 'std::istream'

/////////////////////////////////////////////////////////////////////////////
// Generic implementation (couldn't resist to put one)
template<bool LE,typename T> class ReadChar
{
 public:
    std::istream& operator()(T& c, std::istream& in)
       {
        in.read(buf,bufsiz);
        //const std::streamsize n_read = in ? bufsiz : in.gcount();
        if(!in)
           {// Could not real all bytes
            c = std::char_traits<T>::eof();
           }
        else if constexpr (LE)
           {// Little endian
            c = buf[0];
            for(int i=1; i<bufsiz; ++i) c |= buf[i] << (8*i);
           }
        else
           {// Big endian
            const std::size_t imax = bufsiz-1;
            for(std::size_t i=0; i<imax; ++i) c |= buf[i] << (8*(imax-i));
            c |= buf[imax];
           }
        return in;
       }

 private:
    static constexpr std::size_t bufsiz = sizeof(T);
    unsigned char buf[bufsiz];
};

/////////////////////////////////////////////////////////////////////////////
// Partial specialization for 32bit chars
template<bool LE> class ReadChar<LE,char32_t>
{
 public:
    std::istream& operator()(char32_t& c, std::istream& in)
       {
        in.read(buf,4);
        if constexpr (LE) c = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); // Little endian
        else              c = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; // Big endian
        return in;
       }

 private:
    char buf[4];
};

/////////////////////////////////////////////////////////////////////////////
// Partial specialization for 16bit chars
template<bool LE> class ReadChar<LE,char16_t>
{
 public:
    std::istream& operator()(char16_t& c, std::istream& in)
       {
        in.read(buf,2);
        if constexpr (LE) c = buf[0] | (buf[1] << 8); // Little endian
        else              c = (buf[0] << 8) | buf[1]; // Big endian
        return in;
       }

 private:
    char buf[2];
};

/////////////////////////////////////////////////////////////////////////////
// Specialization for 8bit chars
template<> class ReadChar<false,char>
{
 public:
    std::istream& operator()(char& c, std::istream& in)
       {
        return in.get(c);
       }
};

我用ReadChar实现解析功能:

template<typename T,bool LE> void parse(std::istream& fin)
{
    ReadChar<LE,T> get;
    T c;
    while( get(c,fin) )
       {
        if(c==static_cast<T>('a')) {/* ... */} // Ugly comparison of T with a char literal
       }
}

丑陋的部分是 static_cast 当我需要与字符文字进行比较时。

然后我将 parse 与这个丑陋的样板代码一起使用:

#include <fstream> // 'std::ifstream'
std::ifstream fin("/path/to/file", std::ios::binary);
auto bom = check_bom(fin); // 'check_bom' function is quite trivial
     if( bom.is_empty()  )  parse<char>(fin);
else if( bom.is_utf8() )    parse<char>(fin); // In my case there's no need to handle multi-byte chars
else if( bom.is_utf16le() ) parse<char16_t,true>(fin);
else if( bom.is_utf16be() ) parse<char16_t,false>(fin);
else if( bom.is_utf32le() ) parse<char32_t,true>(fin);
else if( bom.is_utf32be() ) parse<char32_t,false>(fin);
else                        throw std::runtime_error("Unrecognized BOM");

现在,这个解决方案有一些怪癖(不能在 parse 中直接使用字符串文字) 我的问题是是否有解决这个问题的替代方法, 可能会使用我忽略的现有或即将推出的标准设施。

中,我们获得了类型安全的联合。这些可以与 std::visit.

一起用于在运行时和编译时状态之间进行映射
template<auto x>
using constant_t = std::integral_constant<std::decay_t<decltype(x)>, x>;
template<auto x>
constexpr constant_t<x> constant = {};

template<auto...Xs>
using variant_enum_t = std::variant< constant_t<Xs>... >;

enum class EBom {
  None,
  utf8,
  utf16le,
  utf16be,
  utf32le,
  utf32be,
  count,
};
// you could use the existence of EBom::count and the
// assumption of contiguous indexes to automate this as well:
using VEBom = variant_enum< EBom::None, EBom::utf8, EBom::utf16le, EBom::utf16be, EBom::utf32le, EBom::utf32be >;

template<std::size_t...Is>
constexpr VEBom make_ve_bom( EBom bom, std::index_sequence<Is...> ) {
  static constexpr VEBom retvals[] = {
    constant<static_cast<EBom>(Is)>...
  };
  return retvals[ static_cast<std::size_t>(bom) ];
}
constexpr VEBom make_ve_bom( EBom bom ) {
  return make_ve_bom( bom, std::make_index_sequence< static_cast<std::size_t>(EBom::count) >{} );
}

现在,有了运行时 EBom 值,我们可以生成 VEBom.

这样 VEBom 我们可以在编译时得到类型。假设你有特征,比如:

template<EBom>
constexpr boom bom_is_bigendian_v = ???;
template<EBom>
using bom_chartype_t = ???;

您现在可以编写如下代码:

std::visit( vebom, [&](auto bom) {
  bom_chartype_t<bom> next = ???;
  if constexpr (bom_is_bigendian_v<bom>) {
    // swizzle
  }

} );

等等

您的非 DRY 代码

template<bool LE, class char_t> class ReadChar {
public:
  std::istream& operator()(char_t& c, std::istream& in)
  {
    in.read(buf,sizeof(char_t));
    c = buf[0] | (buf[1] << 8);
    if constexpr(!LE)
      reverse_bytes(&c);
    return in;
  }
private:
  char buf[sizeof(char_t)];
};

通过简单的重写变成 DRY。

您的样板文件变为:

std::ifstream fin("/path/to/file", std::ios::binary);
auto bom = check_bom(fin); // 'check_bom' function is quite trivial
if (bom.invalid())
  throw std::runtime_error("Unrecognized BOM");

auto vebom = make_ve_bom( bom.getEnum() );
std:visit( vebom, [&]( auto ebom ) {
  parse<bom_chartype_t<ebom>, !bom_is_bigendian_v<ebom>>( fin );
});

魔法在别处完成。

这里的神奇之处在于 std::variant 拥有一堆 integral_constants,每个 stateless 并且知道(在其类型中)什么它的价值是。

因此 std::variant 中的唯一状态是它包含哪个无状态枚举值。

std::visit 继续使用 std::variant 中的任何无状态 std::integral_constant 调用传入的 lambda。在那个 lambda 中,我们可以使用它的值 作为编译时间常量 ,就像我们使用任何其他 std::integral_constant.

一样

std::variant 的运行时状态实际上是 EBom 的值,因为我们如何设置它,所以将 EBom 转换为 VEBom 是从字面上复制数字(所以,免费)。神奇之处在于 std::visit,它会自动编写 switch 语句并将每种可能性的编译时间(整数常量)值注入您的代码。

None其中的。其中大部分是 ,我可能也在那里使用了 功能。

以上代码没有编译,只是写出来的。它可能包含拼写错误,但技术是正确的。

--

我们可以自动生成变体类型:

template<class Enum, std::size_t...Is, class VEnum=variant_enum<
  constant_t<static_cast<Enum>(Is)>...
>>
constexpr VEnum make_venum( Enum e, std::index_sequence<Is...> ) {
  static constexpr VEnum retvals[] = {
    constant<static_cast<Enum>(Is)>...
  };
  return retvals[ static_cast<std::size_t>(e) ];
}
template<class Enum>
constexpr auto make_venum( Enum e ) {
  return make_venum( e, std::make_index_sequence< static_cast<std::size_t>(Enum::count) >{} );
}
template<class Enum>
using venum_t = decltype(make_venum( static_cast<Enum>(0) ));

现在我们的 VEBom 只是:

using VEBom = venum_t<EBom>;

无论如何,live example 打字错误已修复。