十六进制字符到std::string的Constexpr转换

constexpr conversion of hex chars to std::string

本文关键字:Constexpr 转换 string 十六进制 std 字符      更新时间:2023-10-16

我有很多这样的字符串:

"343536"_hex

,我想把它们转换成相应的字节串。我使用c++ 11,并定义了一个用户定义的字符串字面值来将这些字面值转换为十六进制字符串。然而,我目前拥有的转换不能被评估为我正在寻找的constexpr。特别是我想使用这样的东西,但作为constexpr:

std::string operator "" _hex(const char *s, std::size_t slen )
{
    std::string str;
    str.reserve(slen);
    char ch[3];
    unsigned long num;
    ch[2] = '';
    for ( ; slen; slen -= 2, s += 2) {
        ch[0] = s[0];
        ch[1] = s[1];
        num = strtoul(ch, NULL, 16);
        str.push_back(num);
    }
    return str;
}

测试驱动程序

int main()
{
    std::string src{"653467740035"_hex};
    for (const auto &ch : src)
        std::cout << std::hex << std::setw(2) << std::setfill('0') 
                  << (unsigned)ch << 'n';
}

的示例输出
65
34
67
74
00
35

要非常非常清楚地说明我的问题,是这样的:如何编写这种类型的c++ 11字符串文字转换,可以在编译时作为constexpr进行评估?

为了实现您正在尝试做的事情,您将需要一些与constexpr兼容的编译时string类。但并没有这样的标准。我可以看到一些接近它的东西:

  • boost::mpl::string,但是界面不是很漂亮。
  • boost::log::string_literal,它有你想要的接口,但缺乏constexpr支持。
  • std::string_literal,这正是你正在寻找的,但它没有实现。如果你有一些空闲时间,它可能可以在c++ 11中实现。

为了简化这一切,让我们使用一个过度简化的string_literal类。请注意,上面描述的一些类后面有一个,以接近std::string,但我们不会费心添加一个。

template<std::size_t N>
struct string_literal
{
    char data[N];
};

我们还将提供operator+用于连接。这需要一些时间来解释它是如何工作的,这与这个问题无关。假设它只是一些模板巫术(std::integer_sequence是一个c++ 14实用程序,但可以在c++ 11中实现):

template<std::size_t N1, std::size_t N2, std::size_t... Ind1, std::size_t... Ind2>
constexpr auto concatenate(string_literal<N1> lhs, string_literal<N2> rhs,
                   std::index_sequence<Ind1...>, std::index_sequence<Ind2...>)
    -> string_literal<N1+N2>
{
    return { lhs.data[Ind1]... , rhs.data[Ind2]... };
}
template<std::size_t N1, std::size_t N2>
constexpr auto operator+(string_literal<N1> lhs, string_literal<N2> rhs)
    -> string_literal<N1+N2>
{
    using Indices1 = std::make_index_sequence<N1>;
    using Indices2 = std::make_index_sequence<N2>;
    return concatenate(lhs, rhs, Indices1{}, Indices2{});
}

您可以使用模板用户定义的文字(char...)来摆脱字符串文字,并拥有更漂亮的文字(1234_hex而不是"1234"_hex):

template<char... Chars>
auto operator "" _hex()
    -> string_literal<sizeof...(Chars)/2>
{
    return process<Chars...>();
}

现在,您所需要的是一个可以按对处理字符的函数。一个通用的和"完成"条件的重载。请注意,enable_if_t是为了避免模棱两可的函数调用(这是c++ 14,但您可以在c++ 11中用typename std::enable_if<...>::type替换它)。将字符转换为等效数字的"实际"工作是在process重载中完成的,该重载只接受两个模板实参。

template<char C1, char C2>
constexpr auto process()
    -> string_literal<1>
{
    return { 16 * (C1 - '0') + (C2 - '0') };
}
template<char C1, char C2, char... Rest,
         typename = std::enable_if_t< (sizeof...(Rest) > 0), void >>
constexpr auto process()
    -> string_literal<sizeof...(Rest)/2 + 1>
{
    return process<C1, C2>() + process<Rest...>();
}

你可以添加更多的检查来确保总是有偶数个字符,或者确保没有任何坏字符。我提供的代码使用了c++ 14标准库中的一些特性,但我确保只使用了在需要时可以在c++ 11中轻松重新实现的特性。注意,由于放宽了对constexpr函数的限制,您可以使用c++ 14编写更易于阅读的程序。

下面是一个c++ 14示例,其中包含了前面提到的所有函数和类。我确保您的测试程序仍然可以工作(我只是用scr.data替换了循环中的scr,因为我们使用了一个经过修饰的string_literal类)。