基于 C++ 中的枚举值转换 void*

cast void* based on enum value in C++

我正在使用 python C Api 在 C++ 中编写一个 Python 库。我有大约 25 个函数,它们都接受两个字符串。由于 Python 可能会将字符串保存在 utf8/16/32 中(此时 char 需要更大的大小,整个字符串将使用更大的大小)。当检查字符串是哪种类型时,您会得到一个介于 0 和 4 之间的枚举值。0/4 应作为 utf32 处理,1 应作为 utf8 处理,2 应作为 utf16 处理。所以我目前每个组合都有一个嵌套开关:

以下示例显示了我的代码中元素的处理方式。 random_func 对于我的每个函数都是不同的,它是一个模板,可以接受任何类型的 string_view。这种编写代码的方式会为每个接受两个字符串的函数生成大约 100 行样板文件。

有没有一种方法可以处理所有这些情况,而不会出现这种巨大的代码重复并且不会牺牲性能?

double result = 0;
Py_ssize_t len_s1 = PyUnicode_GET_LENGTH(py_s1);
void* s1 = PyUnicode_DATA(py_s1);

Py_ssize_t len_s2 = PyUnicode_GET_LENGTH(py_s2);
void* s2 = PyUnicode_DATA(py_s2);

int s1_kind = PyUnicode_KIND(py_s1);
int s2_kind = PyUnicode_KIND(py_s2);

switch (s1_kind) {
case PyUnicode_1BYTE_KIND:
    switch (s2_kind) {
    case PyUnicode_1BYTE_KIND:
        result = random_func(
            basic_string_view<char>(static_cast<char*>(s1), len_s1),
            basic_string_view<char>(static_cast<char*>(s2), len_s2));
        break;
    case PyUnicode_2BYTE_KIND:
        result = random_func(
            basic_string_view<char>(static_cast<char*>(s1), len_s1),
            basic_string_view<char16_t>(static_cast<char16_t*>(s2), len_s2));
        break;
    default:
        result = random_func(
            basic_string_view<char>(static_cast<char*>(s1), len_s1),
            basic_string_view<char32_t>(static_cast<char32_t*>(s2), len_s2));
        break;
    }
    break;
case PyUnicode_2BYTE_KIND:
    switch (s2_kind) {
    case PyUnicode_1BYTE_KIND:
        result = random_func(
            basic_string_view<char16_t>(static_cast<char16_t*>(s1), len_s1),
            basic_string_view<char>(static_cast<char*>(s2), len_s2));
        break;
    case PyUnicode_2BYTE_KIND:
        result = random_func(
            basic_string_view<char16_t>(static_cast<char16_t*>(s1), len_s1),
            basic_string_view<char16_t>(static_cast<char16_t*>(s2), len_s2));
        break;
    default:
        result = random_func(
            basic_string_view<char16_t>(static_cast<char16_t*>(s1), len_s1),
            basic_string_view<char32_t>(static_cast<char32_t*>(s2), len_s2));
        break;
    }
    break;
default:
    switch (s2_kind) {
    case PyUnicode_1BYTE_KIND:
        result = random_func(
            basic_string_view<char32_t>(static_cast<char32_t*>(s1), len_s1),
            basic_string_view<char>(static_cast<char*>(s2), len_s2));
        break;
    case PyUnicode_2BYTE_KIND:
        result = random_func(
            basic_string_view<char32_t>(static_cast<char32_t*>(s1), len_s1),
            basic_string_view<char16_t>(static_cast<char16_t*>(s2), len_s2));
        break;
    default:
        result = random_func(
            basic_string_view<char32_t>(static_cast<char32_t*>(s1), len_s1),
            basic_string_view<char32_t>(static_cast<char32_t*>(s2), len_s2));
        break;
    }
    break;
}

使用变体将复杂性放在函数中

using python_string_view = std::variant<std::basic_string_view<char>,
    std::basic_string_view<char16_t>,
    std::basic_string_view<char32_t>;

python_string_view decode_python_string(python_string py_str)
{
    Py_ssize_t len_s = PyUnicode_GET_LENGTH(py_str);
    void* s = PyUnicode_DATA(py_str);
    int s_kind = PyUnicode_KIND(py_str);

    switch (s_kind) {
        //return correct string_view here
    }
}

int main()
{
    python_string s1 = ..., s2 = ...;
    auto v1 = decode_python_string(s1);
    auto v2 = decode_python_string(s2);
    std::visit([](auto&& val1, auto&& val2) {
        random_func(val1, val2);
    }, v1, v2);
}

虽然我不确定性能。

物有所值:

具有不同 char 类型的不同之处在于,在您提取 random_func 中的字符值时(如果我是对的,需要九个模板专业化)。

通过在所有情况下使用最大类型获取字符并在必要时屏蔽或移出额外字节,您将接近解决方案。您将传递合适的掩码和步幅信息,而不是模板。像

for (char32_t* c= (char32_t*)s1; c &= mask, c != 0; c= (char32_t*)((char*)c + stride))
{
    …
}

不幸的是,不算额外的屏蔽操作,你碰壁了,因为你可能不得不在字符串的一端获取太多字节,导致非法内存访问。