问题描述
有时我必须解析各种编码的文本文件, 我想知道即将出台的标准是否会为此带来一些工具 因为我对我目前的解决方案不是很满意。 我什至不确定这是否是正确的方法,但是 我定义了一个函子模板来从流中提取一个字符:
#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,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,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
中直接使用字符串文字)
我的问题是是否有其他方法可以解决这个问题,
也许使用我忽略的现有或即将推出的标准设施。
解决方法
在 c++17 中,我们获得了类型安全联合。这些可以与 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
,每个 std::variant
都是无状态并且知道(在其类型中)它的值是什么。
所以 std::visit
中的唯一状态是它包含的无状态枚举值。
std::integral_constant
继续使用 std::variant
中的无状态 std::integral_constant
调用传入的 lambda。在那个 lambda 中,我们可以使用它的值作为编译时常量,就像我们使用任何其他 std::variant
一样。
EBom
的运行时状态实际上是 EBom
的值,因为我们如何设置它,因此将 VEBom
转换为 std::visit
实际上是复制数量超过(所以,免费)。神奇之处在于 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) ));
,它会自动编写 switch 语句并将每种可能性的编译时间(积分常数)值注入到您的代码中。
这些都不是c++23。其中大部分是 c++17,我可能也在其中使用了 c++20 功能。
上面的代码没有编译,只是写的。它可能包含错别字,但技术是合理的。
--
我们可以自动制作变体类型:
VEBom
现在我们的 using VEBom = venum_t<EBom>;
只是:
let config = {
user: 'user',password: 'password',server: 'server',database: 'database',"options":{
instanceName: 'instanceName',"encrypt":true,"enableArithAbort":true,"trustServerCertificate": true,}
};
module.exports=config;
无论如何,修正了错别字的live example。