C 语言中的泛型类型转换

Generic type conversion in C

提问人:user1806687 提问时间:8/27/2022 最后编辑:user1806687 更新时间:8/27/2022 访问量:192

问:

我有一个数据类型列表()。我想自动(使用 X 宏)创建从其中一种类型转换为另一种类型的函数。STANDARD_TYPES

我有以下代码:

#define STANDARD_TYPES(macro)   \
    macro(char)                 \
    macro(uchar)                \
    macro(schar)                \
    macro(short)                \
    macro(ushort)               \
    macro(int)                  \
    macro(uint)                 \
    macro(long)                 \
    macro(ulong)                \
    macro(longlong)             \
    macro(ulonglong)            \
    macro(float)                \
    macro(double)               \
    macro(longdouble)


#define Y(to_type, from_type)                                                                                                    \
    case TYPE_##from_type:                                          \
        ((struct result_##to_type *)result)->value =                \
            (to_type)((struct result_##from_type *)result)->value;  \
        break;
            
#define X(to_type)                                                                                                      \
void convert_to_##to_type(struct result *result)    \
{                                                   \
    switch (result->type) {                         \
        Y(to_type, long)                            \
    }                                               \
                                                    \
    return result;                                  \
}

STANDARD_TYPES(X)
#undef X
#undef Y

struct result_X为每种类型生成,如下所示:

struct result {
    enum type type;
};

struct result_char {
    enum type type;
    char value;
};

struct result_long {
    enum type type;
    long value;
};

通过上面的代码示例,我可以生成从数据类型转换为任何其他数据类型的函数。例如,上述代码示例的输出为:charlong

void convert_to_long(struct result *result)
{
    switch (result->type) {
    case TYPE_char:
        ((struct result_long *)result)->value = (long)((struct result_char *)result)->value;
        break;
    }
}

我可以用什么替换代码或代码的更多部分,使其在所有定义的数据类型之间生成转换函数?Y(to_type, char)

编辑:

enum type {
    TYPE_char,
    TYPE_uchar,
    TYPE_schar,
    ...
    TYPE_long,
    ...
};

编辑2:

为了澄清一些事情,我将简要解释我的代码试图实现的目标。在用户端,也就是使用我的代码的人,他们正在执行一些数学运算,完成后,他们将结果与结果的类型和值一起写入。struct result *result

然后,我的代码应将 的值从包含的类型转换为请求的类型,该类型可以是任何标准类型。struct result *resultstruct result *result

void convert_result(struct result *result, enum type new_type)
{
    switch (new_type) {
    case TYPE_char:
        convert_to_char(result);
        break;
    ...
    case TYPE_long:
        convert_to_long(result);
        break;
};
泛型 C 预处理器 X-宏

评论

0赞 Dmytro 8/27/2022
为什么不像 VARIANT 那样直接使用标记的联合呢?
0赞 user1806687 8/27/2022
@Dmitry 因为类型可以扩展到标准之外。用户可以定义他们想要的任何类型。这也是为什么会这样的原因。struct result
0赞 0___________ 8/27/2022
指针双关语调用未定义的行为。调用 UB 时,您的代码可能像往常一样工作,也可能不正常。
1赞 n. m. could be an AI 8/27/2022
整个事情看起来就像它以复杂的方式做了一件简单的事情。为什么需要一个函数来从一种数据类型转换为另一种数据类型?内置转化(即隐式转化和强制转换)有什么问题?
1赞 n. m. could be an AI 8/27/2022
@0___________我在此代码中没有看到任何字节数组。我所看到的只是简单的投射(注意不是)等。唯一涉及的双关语类型是在结构的不同品牌之间。charchar*longresult

答:

-1赞 0___________ 8/27/2022 #1

当您违反严格的别名规则时,您的代码会调用未定义的行为。

要“转换”(它称为类型双关)char数组到其他类型(假设汽车数组包含目标类型的正确字节表示)玩具需要它。此外,您的奇怪枚举也无法正常工作。memcpy

例:

long long puneToLongLong(const char *buff)
{
    long long result;
    memcpy(&result, buff, sizeof(result));
    return result;
}

double puneToDouble(const char *buff)
{
    double result;
    memcpy(&result, buff, sizeof(result));
    return result;
}

int puneTo(const char *buff)
{
    int result;
    memcpy(&result, buff, sizeof(result));
    return result;
}

int main(void)
{
    char cd[sizeof(double)];
    char cll[sizeof(long long)];
    char ci[sizeof(int)];

    memcpy(cd, &(double){5.45676545}, sizeof(cd));
    memcpy(cll, &(long long){453676457654}, sizeof(cll));
    memcpy(ci, &(long long){-12454354}, sizeof(ci));

    printf("%f\n", puneToDouble(cd));
    printf("%lld\n", puneToLongLong(cll));
    printf("%i\n", puneToInt(ci));
}


评论

0赞 user1806687 8/27/2022
我上面的例子有效。我违反了什么规则?我不明白。
0赞 0___________ 8/27/2022
@user1806687你做指针双关语。这是未定义的行为。
0赞 aulven 8/27/2022
@user1806687。未定义的行为通常看起来有效,直到它不起作用。
1赞 Eric Postpischil 8/27/2022
(a) 这不能解决已发布的关于宏替换或“自动”定义功能的问题。(b) 它错误地声明违反了别名规则。问题中的代码不包含任何别名冲突。根据 C 2018 6.5.2.3 6,如果定义了合适的并集,则显示的代码可以在没有混叠冲突的情况下使用。(c) 答复中显示的代码没有提供实现问题所要求的转换的任何方法;这似乎是对要解决的问题的误解。
2赞 Eric Postpischil 8/27/2022 #2

我认为没有办法获得在自身内部应用 X 宏的所需行为,因为递归替换被 C 2018 6.10.3.4 2 抑制了。但是,如果定义两个 X 宏,则可以执行以下操作:

#define ApplyToTypes(macro) \
    macro(char)  \
    macro(short) \
    macro(int)

#define ApplyToTypes2(ToType, macro) \
    macro(ToType, char)  \
    macro(ToType, short) \
    macro(ToType, int)


#define Case(ToType, FromType)                                     \
    case TYPE_##FromType:                                          \
        ((struct result_##ToType *) result)->value =               \
            (ToType) ((struct result_##FromType *) result)->value; \
        break;

#define Routine(ToType) \
    void ConvertTo##ToType(struct result *result) \
    {                                             \
        switch (result->type)                     \
        {                                         \
            ApplyToTypes2(ToType, Case)           \
        }                                         \
    }

ApplyToTypes(Routine)

这被替换为(为了便于阅读,添加了手动格式):

void ConvertTochar(struct result *result)
{
    switch (result->type)
    {
        case TYPE_char: ((struct result_char *) result)->value = (char) ((struct result_char *) result)->value;
            break;
        case TYPE_short: ((struct result_char *) result)->value = (char) ((struct result_short *) result)->value;
            break;
        case TYPE_int: ((struct result_char *) result)->value = (char) ((struct result_int *) result)->value;
            break;
    }
}
void ConvertToshort(struct result *result)
{
    switch (result->type)
    {
        case TYPE_char: ((struct result_short *) result)->value = (short) ((struct result_char *) result)->value;
            break;
        case TYPE_short: ((struct result_short *) result)->value = (short) ((struct result_short *) result)->value;
            break;
        case TYPE_int: ((struct result_short *) result)->value = (short) ((struct result_int *) result)->value;
            break;
    }
}
void ConvertToint(struct result *result)
{
    switch (result->type)
    {
        case TYPE_char: ((struct result_int *) result)->value = (int) ((struct result_char *) result)->value;
            break;
        case TYPE_short: ((struct result_int *) result)->value = (int) ((struct result_short *) result)->value;
            break;
        case TYPE_int: ((struct result_int *) result)->value = (int) ((struct result_int *) result)->value;
            break;
    }
}

要提供由 C 标准定义的此代码行为,您可以定义一个包含所有结构的联合,以便 C 2018 6.5.2.3 6 适用。1 但是,简单地定义一个包含联合的结构并重写代码以匹配可能会更好:result

struct result
{
    enum type type;
    union
    {
        char  char_value;
        short short_value;
        int   int_value;
        …
    };
};

例如:

#define ApplyToTypes(macro) \
    macro(char)  \
    macro(short) \
    macro(int)

#define ApplyToTypes2(ToType, macro) \
    macro(ToType, char)  \
    macro(ToType, short) \
    macro(ToType, int)

#define DeclareEnumConstants(Type)  Type##Enum,
enum type { ApplyToTypes(DeclareEnumConstants) };

#define DeclareMember(Type) Type Type##Value;
struct result
{
    enum type type;
    union
    {
        ApplyToTypes(DeclareMember)
    };
};

#define Case(ToType, FromType)                                    \
    case FromType##Enum:                                          \
        result->ToType##Value = (ToType) result->FromType##Value; \
        break;

#define Routine(ToType) \
    void ConvertTo##ToType(struct result *result) \
    {                                             \
        switch (result->type)                     \
        {                                         \
            ApplyToTypes2(ToType, Case)           \
        }                                         \
    }

ApplyToTypes(Routine)

脚注

1 C 2018 6.5.2.3 6说道:

...如果一个并集包含多个共享一个共同初始序列的结构...,则允许在可见并集完整类型的声明的任何地方检查其中任何一个的共同初始部分...

同样相关的还有C 2018 67.2.1 16:

...指向联合对象的指针(经过适当转换)指向其每个成员(或者,如果成员是位域,则指向它所在的单元),反之亦然。

评论

0赞 user1806687 8/27/2022
谢谢。我使用了多个结构,因为:1)我想节省内存。如果我只需要 128 个字节,就不想创建一个 1 字节。2)我想要一个选项,让用户定义一个自定义,我想对于2)解决方法可能是在联合内部有一个空隙*。而 128bytes 并不多。如果您对 1) 和 2) 有更多澄清,我很乐意听到它们。今天晚些时候,当我有机会测试它时,我会接受这个答案。谢谢!long doubleboolstruct result_custom { enum type type; /* custom members */ };
0赞 Support Ukraine 8/27/2022
@user1806687 关于你的第 1 点),即节省内存......为此,调用方必须提供指向足够大的内存的指针,以容纳最大的目标结构。如果您希望能够处理所有转换,那么我认为所有对象都必须具有最大大小。ConvertTo
0赞 user1806687 8/27/2022
@SupportUkraine 如果调用方只使用 int,则分配长双精度内存可能效率低下。就我而言,这很好,并将使用联合方法。
0赞 Support Ukraine 8/27/2022
@user1806687这是真的,所以你的想法是对你的转换函数进行限制,告诉它是调用者的责任,传递的指针指向足够的内存来保存目标类型 - 否则它将是未定义的行为......是这样吗?
0赞 Support Ukraine 8/27/2022
引用的 C 2018 67.2.1 清楚地表明,指向例如 可以转换为指向 (的第一个成员。据我所知,ides 是调用者将拥有例如 a 并在调用 convert 函数时将其转换为。即使两者都有一个作为第一个成员,它似乎是 C 2018 67.2.1 解决的不同转换。我错了吗?你能详细说明一下吗?struct resultenum typeenum typestruct_result_charstruct resultstruct resultstruct result_charenum type