【问题标题】:Compile-Time Base64 Decoding in C++C++ 中的编译时 Base64 解码
【发布时间】:2020-04-22 22:44:05
【问题描述】:

是否可以在编译时将 base64 编码数据解码为二进制数据?

我想到的东西看起来像这样:

constexpr auto decoded = decodeBase64<"SGVsbG8=">();

constexpr auto decoded = decodeBase64("SGVsbG8=");

我对 decoded 的结果类型没有特殊要求。

【问题讨论】:

  • constexpr auto decoded = decodeBase64<"SGVsbG8=">(); - const char[] 不能是从 C++17 开始的非类型模板参数。 constexpr auto decoded = decodeBase64("SGVsbG8="); - ,如果 decodeBase64 采用 const char* 并且是 constexpr 函数。
  • 尝试制作一个简单的解码器,将字符串作为常规参数,并将constexpr放在它前面。它应该工作。如果您遇到更具体的问题,请在 StackOverflow 上再次询问。
  • @Fureeish:并不是说你不能拥有那种类型的模板参数(调整为const char*或通过指针或对数组的引用);您只是不能将字符串文字用作模板参数

标签: c++ c++17 constexpr compile-time


【解决方案1】:

我发现在 google 上搜索 constexpr base64 解码器非常困难,所以我在这里调整了一个: https://gist.github.com/tomykaira/f0fd86b6c73063283afe550bc5d77594

因为这是 MIT 许可的 (sigh),所以一定要在源文件中的某个地方加上这个:

/**
 * The MIT License (MIT)
 * Copyright (c) 2016 tomykaira
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

要从constexpr 函数返回字符串,您需要返回一个字符数组。因为您不能返回数组或std::string,所以std::array 是最好的选择。但是有一个问题 - 由于 standards oversight,直到 C++17,std::array[] 运算符是非常量的。你可以通过继承和添加构造函数来解决这个问题:

template <size_t N>
struct fixed_string : std::array<char, N> {
    constexpr fixed_string(const char (&input)[N]) : fixed_string(input, std::make_index_sequence<N>{}) {}
    template <size_t... Is>
    constexpr fixed_string(const char (&input)[N], std::index_sequence<Is...>) : std::array<char, N>{ input[Is]... } {}
};

更改解码器以使用它而不是 std::string,它似乎可以作为 constexpr 工作。需要 C++14,因为 C++11 constexpr 函数只能有一个 return 语句:

template <size_t N>
constexpr const std::array<char, ((((N-1) >> 2) * 3) + 1)> decode(const char(&input)[N]) {
    constexpr unsigned char kDecodingTable[] = {
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
        64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
        64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
    };

    static_assert(((N-1) & 3) == 0, "Input data size is not a multiple of 4");

    char out[(((N-1) >> 2) * 3) + 1] {0};

    size_t out_len = (N-1) / 4 * 3;
    if (input[(N-1) - 1] == '=') out_len--;
    if (input[(N-1) - 2] == '=') out_len--;

    for (size_t i = 0, j = 0; i < N-1;) {
      uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
      uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
      uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
      uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];

      uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);

      if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
      if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
      if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
    }
    return fixed_string<(((N-1) >> 2) * 3) + 1>(out);
}

用法:

constexpr auto x = decode("aGVsbG8gd29ybGQ=");
/*...*/
printf(x.data()); // hello world

演示:https://godbolt.org/z/HFdk6Z

已更新以解决来自 Marek R 和 Frank 的有用反馈

【讨论】:

  • IMO 返回值应该是 std::array 而不是自定义类。还应该指出,这段代码需要c++14
  • 真的很好,但我不确定fixed_string中数据的大小是否正确。如果没有,则不考虑一两个“=”填充字符。
  • @MarekR 无论出于何种原因,直到 C++17,std::array 的索引运算符都是非常量的。你是对的,如果你使用 C++17 或更高版本,那会更干净。
  • @Frank 关于输出长度的好点!我会更新答案,但它仍然必须使用更简单的计算来分配缓冲区,因为模板参数不能使用函数参数内容(例如字符串)。
  • @DavisHerring 理想情况下,我会声明一个std::array,使用它并返回它,但限制意味着我必须在 C 数组中构建输出并将其复制到 std::array 中。 std::array 没有为此的构造函数或任何用户构造函数,而是像 C 数组一样聚合初始化。因此,fixed_string 辅助类现在所做的就是添加一个构造函数,将 C 数组的内容传输到 std::array 的大括号初始化器中。constexpr 函数仍然输出 std::array
【解决方案2】:

parktomatomi 的回答对找到此解决方案有很大帮助。 使用 C++17 和 std::array 这似乎可行。

base64解码器是基于https://stackoverflow.com/a/34571089/3158571的答案

constexpr size_t decodeBase64Length(const char *s)
{
    size_t len = std::char_traits<char>::length(s);
    if (s[len - 2] == '=')
        return (len / 4) * 3 - 2;
    else if(s[len -1] == '=')
        return (len / 4) * 3 - 1;
    else
        return (len / 4) * 3 ;
}

constexpr std::array<int, 256> prepareBase64DecodeTable() {
    std::array<int, 256> T{ 0 }; // breaks constexpr: T.fill(-1) or missing initialization
    for (int i = 0; i < 256; i++)
        T[i] = -1;
    for (int i = 0; i < 64; i++)
        T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
    return T;
}

// based on https://stackoverflow.com/a/34571089/3158571
template<int N>
constexpr std::array<std::byte, N> decodeBase64(const char *b64Str)
{
    constexpr auto T = prepareBase64DecodeTable();
    std::array<std::byte, N> out = { std::byte(0) };
    int valb = -8;
    for (size_t i = 0, val = 0, posOut = 0; i < std::char_traits<char>::length(b64Str) && T[b64Str[i]] != -1; i++) {
        val = (val << 6) + T[b64Str[i]];
        valb += 6;
        if (valb >= 0) {
            out[posOut++] = std::byte((val >> valb) & 0xFF);
            valb -= 8;
        }
    } 
    return out;
}

使用并不完美,因为如果不将其作为模板参数显式传递,我无法推断出结果数组的长度:

#define B64c "SGVsbG8xMg=="
constexpr auto b64 = decodeBase64<decodeBase64Length(B64c)>(B64c);  // array<byte,7>

https://godbolt.org/z/-DX2-m的演示

【讨论】:

  • 代替const char *,使用对数组的引用从参数中推断出长度:const char (&amp;b64Str)[N]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
  • 2012-12-05
  • 2023-03-16
  • 1970-01-01
相关资源
最近更新 更多