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

Допустим, у нас есть следующие параметры, описывающие человека: имя (строка) и возраст (целое без знака). Я хочу написать универсальную 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
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
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});

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