Неявное приведение к типу объединения?

Допустим, у нас есть следующие параметры, описывающие человека: имя (строка) и возраст (целое без знака). Я хочу написать универсальную API-функцию сеттера, которую кто-то может вызвать, чтобы установить имя или возраст конкретного человека. Причина этого будет объяснена ниже.

Что я сделал, так это определил тип перечисления имен параметров человека:

typedef enum person_param_name
{
    NAME,
    AGE,
} person_param_name_t;

А также тип объединения для значений параметров человека:

typedef union person_param_val
{
    char* name;
    unsigned int age;
} person_param_val_t;

Теперь функция может выглядеть так:

int set_person_param(person_param_name_t param_name, person_param_val_t param_val)
{
    int ret = 0;

    switch (param_name)
    {
        case NAME:
            g_person_name = param_val.name;
            break;
        case AGE:
            g_person_age = param_val.age;
            break;
        default:
            ret = -1;
            break;
    }

    return ret;
}

Проблема с этим подходом заключается в том, что нельзя просто вызвать функцию установки следующим образом (компилятор выдает предупреждение):

set_person_param(NAME, "Alex");
set_person_param(AGE, 5);

Но они должны явно привести значение параметра к типу person_param_val_t, например:

set_person_param(NAME, (person_param_val_t)"Alex");
set_person_param(AGE, (person_param_val_t )5);

Причина, по которой мне нужна универсальная функция установки, заключается в том, что в реальной программе у меня намного больше параметров (около 100), и мне нужно было бы написать много (очень похожих) функций установки, которые заняли бы намного больше строк кода.

Есть ли лучший подход к этому?

Знаете ли вы, что установка age уничтожит name и наоборот?

0___________ 10.01.2023 23:46

Звучит как работа для _Generic

Barmar 10.01.2023 23:46

@Barmar _Generic устроит беспорядок. На данный момент я не уверен, знает ли ОП, что такое союз.

0___________ 10.01.2023 23:47

@0___________ Они назначают разные переменные, поэтому ничего не уничтожается.

Barmar 10.01.2023 23:47

Тогда я не могу понять, почему два разных союза вместо одной структуры. Это похоже на проблему XY для меня

0___________ 10.01.2023 23:48

Вы можете использовать составные литералы: set_person_param(NAME, (person_param_val_t){ .name = "Alex"}); set_person_param(AGE, (person_param_val_t ){ .age = 5} );

tstanisl 10.01.2023 23:51

«Мне нужно было бы написать много (очень похожих) функций установки, которые заняли бы намного больше строк кода», 4 строки для функции настройки, а не 3 строки для добавления случая не намного больше, и это более понятно .

dbush 10.01.2023 23:58

@dbush ты понимаешь, почему объединение и странные глобальные переменные

0___________ 11.01.2023 00:12

Что касается кода вашего примера, то ни (person_param_val_t)"Alex", ни (person_param_val_t )5 не будут работать. Стандарт C определяет приведение только к void или скалярным типам, а не к объединениям (или структурам или массивам).

Eric Postpischil 11.01.2023 00:42

Звучит как работа для простого макроса, никаких союзов или подобных причудливых вещей

pm100 11.01.2023 00:44

У людей нет ни имени, ни возраста. У них есть и имя, и возраст. И вполне возможно иметь возраст и имя одновременно. Таким образом, было бы более разумно хранить и возраст, и имя в структуре, а затем найти способ пометить любой элемент недействительным. Например, оба члена могут быть указателями на данные, установленные в NULL, если они не инициализированы.

Lundin 11.01.2023 08:45

@EricPostpischil Приведение к объединениям не является частью стандарта C, но расширение GNU C позволяет это делать.

A6SE 11.01.2023 09:24

@A6SE: если вы хотите разрешить расширения определенного компилятора, это должно быть указано в вопросе и добавлено как тег. Значение тега C по умолчанию — стандарт: «Этот тег следует использовать с общими вопросами, касающимися языка C, как определено в стандарте ISO 9899 (последняя версия, 9899:2018, если не указано иное…)».

Eric Postpischil 11.01.2023 12:09
Ускорьте разработку веб-приложений Laravel с помощью этих бесплатных стартовых наборов
Ускорьте разработку веб-приложений Laravel с помощью этих бесплатных стартовых наборов
Laravel - это мощный PHP-фреймворк, используемый для создания масштабируемых и надежных веб-приложений. Одним из преимуществ Laravel является его...
Что такое двойные вопросительные знаки (??) в JavaScript?
Что такое двойные вопросительные знаки (??) в JavaScript?
Как безопасно обрабатывать неопределенные и нулевые значения в коде с помощью Nullish Coalescing
Создание ресурсов API Laravel: Советы по производительности и масштабируемости
Создание ресурсов API Laravel: Советы по производительности и масштабируемости
Создание API-ресурса Laravel может быть непростой задачей. Она требует глубокого понимания возможностей Laravel и лучших практик, чтобы обеспечить...
Как сделать компонент справочного центра с помощью TailwindCSS
Как сделать компонент справочного центра с помощью TailwindCSS
Справочный центр - это веб-сайт, где клиенты могут найти ответы на свои вопросы и решения своих проблем. Созданный для решения многих распространенных...
Асинхронная передача данных с помощью sendBeacon в JavaScript
Асинхронная передача данных с помощью sendBeacon в JavaScript
В современных веб-приложениях отправка данных из JavaScript на стороне клиента на сервер является распространенной задачей. Одним из популярных...
Как подобрать выигрышные акции с помощью анализа и визуализации на Python
Как подобрать выигрышные акции с помощью анализа и визуализации на Python
Отказ от ответственности: Эта статья предназначена только для демонстрации и не должна использоваться в качестве инвестиционного совета.
2
13
64
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я мог видеть:

typedef enum person_param_name {
    NAME,
    AGE,
} person_param_name_t;

typedef union person_param_val
{
    char* name;
    unsigned int age;
} person_param_val_t;

person_param_val_t person_param_val_init_charp(char *name) {
    return (person_param_val_t){.name=name};
}
person_param_val_t person_param_val_init_u(unsigned age) {
    return (person_param_val_t){.age=age};
}
#define MAKE_PERSON_PARAM_VAL(x)  _Generic((x) \
    , unsigned: person_param_val_init_u \
    , char *:person_param_val_init_charp \
    )(x)

int set_person_param(person_param_name_t param_name, person_param_val_t param_val);

#define set_person_param(a, b) \
    set_person_param(a, MAKE_PERSON_PARAM_VAL(b))
    
int main() {
    set_person_param(NAME, "Alex");
    set_person_param(AGE, 5u);
}

С GCC с расширением вы получите только:

#define set_person_param(a, b) \
     set_person_param(a, (person_param_val_t)(b))

Но я бы не стал писать такой код. Это C. В C вы бы написали все это явно. Не вижу смысла в person_param_name. Вам все равно придется явно перечислять все типы внутри set_person_param. Я бы просто написал set_person_param_age(unsigned age) и set_person_param_name(char *name) явно. Если нет, я бы подумал о переосмыслении всего подхода, поскольку, скорее всего, вы хотите реализовать виртуальную функцию. Я бы посоветовал настоятельно не писать интерфейс с бесконечным количеством случаев в перечислениях, потому что вы можете получить это. Вместо этого создайте объекты с указателем на интерфейс, хранящийся в виртуальной таблице.

Где волшебные g_... переменные, как в примере OP

0___________ 11.01.2023 00:14

Вам не нужна никакая магия. этого макроса достаточно

#define set_person_param(param, val)  g_person_##param.param = (val)

И эта примерная функция:

int foo(void)
{
    person_param_val_t g_person_name, g_person_age;

    set_person_param(name, "Alex");
    set_person_param(age, 5);
}

Будет предварительно обработан для:

int foo(void)
{
    person_param_val_t g_person_name, g_person_age;

    g_person_name.name = "Alex";
    g_person_age.age = 5;
}

Насколько я понимаю, это было что-то, что вы хотели заархивировать. Ваш тип перечисления не нужен.

Если вы хотите, чтобы val был того же типа союза, то:

#define set_person_param(param, val)  g_person_##param = (val)

Пример:

    set_person_param(name, (person_param_val_t){.name = "Alex"});
Ответ принят как подходящий

Если вы измените объединение так, чтобы имена полей были идентичны константам перечисления:

typedef union person_param_val
{
    char* NAME;
    unsigned int AGE;
} person_param_val_t;

Затем вы можете создать макрос, который будет передавать правильно инициализированный составной литерал:

#define set_person_param_ext(k,v) \
        set_person_param(k, (person_param_val_t){.k=v})

Тогда это:

set_person_param_ext(NAME, "Alex");
set_person_param_ext(AGE, 5);

Будет расширяться до этого:

set_person_param(NAME, (person_param_val_t){.NAME = "Alex"});
set_person_param(AGE, (person_param_val_t){.AGE=5});

Другие вопросы по теме