cgenericsc-preprocessorx-macros

Generic type conversion in C


I have a list of data types (STANDARD_TYPES). I would like to automatically (using X macros) create functions that would convert from one of those type to another.

I have the following code:

#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 is generated for every type and looks like:

struct result {
    enum type type;
};

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

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

With the above code example I can generate functions to convert from char data type to any other data type. For example, long output for the above code example would be:

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;
    }
}

With what can I replace Y(to_type, char) code or more parts of the code to make it generate conversion functions between all defined data types?

EDIT:

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

EDIT2:

To clear up some things I shall explain briefly what my code is trying to accomplish. On the user side, that is whoever is using my code, they are executing some math operations and when done they write the result to the struct result *result along with the type of the result and the value.

My code shall then convert value of the struct result *result from the type contained in struct result *result to the requested type which can be any of the standard types.

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;
};

Solution

  • I do not think there is a way to get the desired behavior of applying the X Macro within itself, since recursive macro replacement is suppressed by C 2018 6.10.3.4 2. However, if you define two X Macros, this can be done:

    #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)
    

    This is replaced by (with manual formatting added for readability):

    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;
        }
    }
    

    To give this code behavior defined by the C standard, you could define a union containing all the result structures, so that C 2018 6.5.2.3 6 would apply.1 However, it may be nicer simply to define one structure containing a union and rewrite the code to match:

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

    For example:

    #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)
    

    Footnote

    1 C 2018 6.5.2.3 6 says:

    … if a union contains several structures that share a common initial sequence…, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible…

    Also relevant is C 2018 67.2.1 16:

    … A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit-field, then to the unit in which it resides), and vice versa.