Я пишу программу на C, которая применяет к массиву другой тип квантования в зависимости от переменной среды.
Проблема в том, что мне нужно использовать ту же функцию void foo(void* ptr, quant_t type, ...), чтобы что-то делать с ptr, но мне нужно заранее привести ее к правильному типу.
(quant_t — это объект типа перечисления)
Я пытался сделать
void foo(void* ptr, quant_t type, ...){
switch(type){
case UNIF:{
struct unif* my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
struct kmeans* my_ptr = (struct kmeans*) ptr;
break;
}...}
my_ptr->a = bar(...);
my_ptr->b = baz(...);
}
Но это не работает, поскольку объявление my_ptr находится внутри области действия переключателя.
Итак, я попытался сделать что-то вроде этого:
void foo(void* ptr, quant_t type, ...){
void* my_ptr = NULL;
switch(type){
case UNIF:{
my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
my_ptr = (struct kmeans*) ptr;
break;
}...}
my_ptr->a = bar(...);
my_ptr->b = baz(...);
}
Но это все еще не работает.
@Saverio Pasqualoni Вместо оператора переключения используйте операторы if-else для обработки определенных указателей в блоках операторов if-else.
Либо my_ptr всегда должен иметь один и тот же тип (например, подструктура внутри структур unif/kmeans) и быть объявлен перед оператором `switch, либо все операции должны выполняться внутри соответствующих операторов case.
Ваш оператор переключения упрощается до void* my_ptr = ptr;. Неудивительно, что это не помогает.
Смотрите этот ответ вчерашний
Напишите разные версии вашей функции и используйте _Generic внутри макроса, чтобы выбрать правильную версию в зависимости от типа первого аргумента.
Зависят ли возвращаемые значения bar(...) и baz(...) от type? Не могли бы вы вызвать их и сохранить возвращаемые значения в локальных переменных перед оператором switch (type)?
Если у вас есть определенное количество указателей разных типов, рассмотрите union разных указателей, а не указатель void. Может оказаться полезным указатель на виртуальную таблицу всех различных функций вместо switch.
Также поместите обычаи внутри переключателя.





Чтобы ->a работало, компилятор должен знать расположение (помимо прочего) поля относительно указателя. Это смещение должно быть постоянным в C.
Мы можем добиться этой цели, сделав ваши два типа совместимыми с третьим и используя этот третий тип (Base).
typedef struct {
A a;
B b;
} Base;
typedef struct {
Base base;
…
} Unif;
typedef struct {
Base base;
…
} Kmeans;
// `base` doesn't need to be the first field of `Unif` and `Kmeans`
// (unless you want to recuperate the orig ptr from a `Base *` at some point).
void foo( Base *ptr, … ) {
ptr->a = bar( … );
ptr->b = baz( … );
}
Unif *unif = …;
foo( &unif->base, … );
Kmeans *kmeans = …;
foo( &kmeans->base, … );
Это менее безопасно, но мы могли бы назвать его и так:
// `base` must be the first field of `Unif` and `Kmeans`.
void foo( Base *ptr, … ) {
ptr->a = bar( … );
ptr->b = baz( … );
}
Unif *unif = …;
foo( (Base *)unif, … );
Kmeans *kmeans = …;
foo( (Base *)kmeans, … );
Это означает, что мы можем внести несколько изменений, чтобы немного сократить код, необходимый для вызывающей программы.
// `base` must be the first field of `Unif` and `Kmeans`.
void foo( void *ptr_, … ) {
Base *ptr = ptr_;
ptr->a = bar( … );
ptr->b = baz( … );
}
Unif *unif = …;
foo( unif, … );
Kmeans *kmeans = …;
foo( kmeans, … );
Последние два не так безопасны, как оригинал, поскольку они обходят проверки типов посредством явного и неявного приведения типов.
@arfneto, Re "А это была бы полезная вещь.", Да, но совершенно не имеет отношения к вопросу. Не имеет значения, является ли функция OP вариативной или нет. Вопрос не в этом! /// Re «Я никогда бы не подумал, что ОП не спрашивал о том, что я только что объяснил». Извините, но я не могу помочь вам с этой проблемой. Я могу только повторить свой предыдущий совет: велика вероятность, что вы ошибаетесь, если вам придется игнорировать весь текст и половину кода в ФП, чтобы ваша интерпретация имела смысл. /// Когда модеры удаляют ваши комментарии не по теме, это не приглашение их репостить.
Это забавно! Я сам удалил все комментарии, сэр. Это было слишком долго и скучно и ничего не добавляло ни к теме, ни ко мне.
Но это не работает, поскольку объявление my_ptr находится внутри области действия переключателя.
Это не работает, но не только из-за декларации my_ptr. Нельзя просто пересылать ...
Я покажу пример, который может делать или не делать то, что вы хотите. Я не уверен, что понимаю, что вам нужно. В любом случае, если вам нужен диспактер, основанный на типе и указателе, этот может подойти.
Если функции используют аргументы одного и того же типа, работает простой 5-строчный VFT, индексированный по типу.
Основы
typedef enum
{
UNIF,
KMEANS
} Class_t;
typedef struct
{
void (*bar)(const char*, unsigned, ...);
} UNIF_t;
typedef struct
{
void (*baz)(const char*, unsigned, ...);
} KMEANS_t;
// the dispatcher
void foo(Class_t type, void* ptr, unsigned N, ...);
main для этого примераДля тестирования:
UNIF_t реализует bar. Пример bar здесь получает строку для идентификатора и (вариативного) некоторого int и просто отображает значения.KMEANS_t реализует baz. Пример baz здесь получает строку для идентификатора и (вариативного) некоторого char* и просто отображает значения.int main(void)
{
UNIF_t unif = {.bar=for_unif};
KMEANS_t kmeans = {.baz=for_kmeans};
void* p = &unif;
foo(UNIF, p, 4, 1, 2, 3, 4);
foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");
p = &kmeans;
foo(KMEANS, p, 2, "What", "if?");
foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);
return 0;
}
Логика тривиальна:
struct каждого видаfoo()foo() void foo(Class_t type, void* ptr, unsigned N, ...)
{
va_list parms;
va_start(parms, N);
switch (type)
{
case UNIF:
{
((UNIF_t*)ptr)->bar("testing for UNIF", N, parms);
break;
}
case KMEANS:
{
((KMEANS_t*)ptr)->baz("testing for KMEANS", N, parms);
break;
}
default:
fprintf(stderr, "Invalid Type 0x%X!\n", type);
break;
}; // switch()
va_end(parms);
}
Это своего рода C++ функтор. Здесь написано так, потому что я предполагаю, что в структурах больше данных. Если нет, то простой VFT, индексированный по типу, работает нормально.
Единственное, что здесь не распространено, — это построение va_list для использования во внутренних функциях.
UNIF: id is 'testing for UNIF', 4 args
args: (1,2,3,4)
KMEANS: id is 'testing for KMEANS', 3 args
1: 'Stack'
2: 'Overflow'
3: 'C'
KMEANS: id is 'testing for KMEANS', 2 args
1: 'What'
2: 'if?'
UNIF: id is 'testing for UNIF', 8 args
args: (8,7,6,5,4,3,2,1)
#include <stdarg.h>
#include <stdio.h>
typedef enum
{
UNIF,
KMEANS
} Class_t;
typedef struct
{
void (*bar)(const char*, unsigned, ...);
} UNIF_t;
typedef struct
{
void (*baz)(const char*, unsigned, ...);
} KMEANS_t;
// the dispatcher
void foo(Class_t type, void* ptr, unsigned N, ...);
// functions for testing these 'classes'
void for_unif (const char*, unsigned N, va_list parms);
void for_kmeans(const char*, unsigned, va_list parms);
int main(void)
{
UNIF_t unif = {.bar = for_unif};
KMEANS_t kmeans = {.baz = for_kmeans};
void* p = &unif;
foo(UNIF, p, 4, 1, 2, 3, 4);
foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");
p = &kmeans;
foo(KMEANS, p, 2, "What", "if?");
foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);
return 0;
}
void for_unif (const char* id, unsigned N, va_list parms)
{
printf("\nUNIF: id is '%s', %d args\n\targs: (", id, N);
for (size_t i = 0; i < N - 1; i += 1)
printf("%d,", va_arg(parms, int));
printf("%d)\n", va_arg(parms, int));
return;
}
void for_kmeans(const char* id, unsigned N, va_list parms)
{
printf("\nKMEANS: id is '%s', %d args\n", id, N);
for (unsigned i = 0; i < N; i += 1)
printf("%2u: '%s'\n", 1 + i, va_arg(parms, char*));
return;
}
void foo(Class_t type, void* ptr, unsigned N, ...)
{
va_list parms;
va_start(parms, N);
switch (type)
{
case UNIF:
{
((UNIF_t*)ptr)
->bar("testing for UNIF", N, parms);
break;
}
case KMEANS:
{
((KMEANS_t*)ptr)
->baz("testing for KMEANS", N, parms);
break;
}
default:
fprintf(stderr, "Invalid Type 0x%X!\n", type);
break;
}; // switch()
va_end(parms);
}
void foo(void* ptr, quant_t type, ...){
void* my_ptr = NULL;
switch(type){
case UNIF:{
my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
my_ptr = (struct kmeans*) ptr;
break;
}...}
my_ptr->a = bar(...);
my_ptr->b = baz(...);
}
если my_ptr есть void*, то my_ptr->a или my_ptr_b не имеют смысла.
Поскольку bar и baz имеют одинаковую подпись, если все, что вы можете, это просто использовать my_ptr для вызова функции внутри struct unif или struct kmeans, в зависимости от типа, это может быть даже проще, фактически однострочным:
F[type](descr[type], N, parms); используя foo, как показано ниже:
foo() как однострочникvoid foo(Class_t type, void* ptr, unsigned N, ...)
{
va_list parms;
va_start(parms, N);
// clang-format off
const char* descr[] = {
[UNIF]= "type is UNIF",[KMEANS]= "type is KMEANS"};
void(*F[])(const char* id, unsigned N, va_list parms) = {
[UNIF] = ((UNIF_t*)ptr)->bar,
[KMEANS] = ((KMEANS_t*)ptr)->baz
};
F[type](descr[type], N, parms);
va_end(parms);
}
Если внутри structs больше ничего не нужно и нужен только метод, это всего лишь таблица функций, индексированная type: она работает так же, как в примере выше.
#include <stdarg.h>
#include <stdio.h>
typedef enum
{
UNIF,
KMEANS
} Class_t;
typedef struct
{
void (*bar)(const char*, unsigned, ...);
} UNIF_t;
typedef struct
{
void (*baz)(const char*, unsigned, ...);
} KMEANS_t;
// the dispatcher
void foo(Class_t type, void* ptr, unsigned N, ...);
// functions for testing these 'classes'
void for_unif (const char*, unsigned N, va_list parms);
void for_kmeans(const char*, unsigned, va_list parms);
int main(void)
{
UNIF_t unif = {.bar = for_unif};
KMEANS_t kmeans = {.baz = for_kmeans};
void* p = &unif;
foo(UNIF, p, 4, 1, 2, 3, 4);
foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");
p = &kmeans;
foo(KMEANS, p, 2, "What", "if?");
foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);
return 0;
}
void for_unif (const char* id, unsigned N, va_list parms)
{
printf("\nUNIF: id is '%s', %d args\n\targs: (", id, N);
for (size_t i = 0; i < N - 1; i += 1)
printf("%d,", va_arg(parms, int));
printf("%d)\n", va_arg(parms, int));
return;
}
void for_kmeans(const char* id, unsigned N, va_list parms)
{
printf("\nKMEANS: id is '%s', %d args\n", id, N);
for (unsigned i = 0; i < N; i += 1)
printf("%2u: '%s'\n", 1 + i, va_arg(parms, char*));
return;
}
void foo(Class_t type, void* ptr, unsigned N, ...)
{
va_list parms;
va_start(parms, N);
// clang-format off
const char* descr[] = {
[UNIF]= "type is UNIF",[KMEANS]= "type is KMEANS"};
void(*F[])(const char* id, unsigned N, va_list parms) = {
[UNIF] = ((UNIF_t*)ptr)->bar,
[KMEANS] = ((KMEANS_t*)ptr)->baz
};
// clang-format on
F[type](descr[type], N, parms);
va_end(parms);
}
https://stackoverflow.com/questions/78081529/runtime-cast-of-void-pointer
Похоже, вы запутались по пути и в итоге ответили совсем на другой вопрос, чем тот, который был задан. ОП хочет сделать ((UNIF_t*)ptr)->a = bar(); ((UNIF_t*)ptr)->b = baz(); или ((KMEANS_t*)ptr)->a = bar(); ((KMEANS_t*)ptr)->b = baz(); в зависимости от предоставленного аргумента. Но вы ответили, как называть ((UNIF_t*)ptr)->bar() или ((KMEANS_t*)ptr)->baz() в зависимости от аргумента. Это совершенно другое.
Я пока не уверен в этом, но считаю, что это то, о чем спрашивает вопрос, такие строки, как foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);. Здесь вы видите type, указатель и вариативную функцию, вызываемую при проверке типа с перенаправленными вариативными параметрами. В любом случае я включил полный исходный код, и автор, возможно, сможет нам сказать...
Ваш код не поддерживает my_ptr->a = bar(...); my_ptr->b = baz(...);, в этом и весь вопрос. Вы вообще не выполняете задания и вызываете foo и baz условно, а не безусловно.
Что касается «и задание есть». Было бы неправильно, если бы вы сделали это в main, поскольку это то, что foo должно делать. Но вы этого не делаете. Вы не назначаете a и b в main. Или в foo. a и b даже не существуют в вашем коде.
ОП высказался. В следующий раз, когда вам придется игнорировать весь текст в ФП и половину кода в ФП, чтобы ваша интерпретация имела смысл, подумайте о возможности того, что вы, возможно, неправильно поняли вопрос.
На вопрос «вы читаете мой пост?», да. Это не имеет никакого отношения к вопросу. /// Re «Не мог бы ты быть также.», Нет. Сомнений не было. И ОП с тех пор подтвердил. /// Re «Что такое my_ptr->a в исходном сообщении? Что такое a?», поле есть как UNIF_t, так и KMEANS_t. /// Re "Вы заметили, что my_ptr пуст?*", Конечно! Вот почему код не работает. /// Опять же, ОП с тех пор «сказал нам». Ты сказал, что это положит конец. Почему ты продолжаешь?
@ikegami, как говорил ОП? Он вам лично рассказал, что такое a и b? Итак, вы могли бы написать ответ, который даже близко не скомпилирован? Хорошо, тогда. Виноват.
На вопрос «как говорил ОП?», проголосовав за и одобрив ответ. /// Re "Он тебе лично сказал, что такое a и b?", нет, это в вопросе.
Очевидно, вы хотите
my_ptr->a = bar(...);работать сmy_ptrс типомmy_ptr, определенным во время выполнения. C не имеет для этого возможности. Динамический выбор типа во время выполнения отсутствует, за исключением длины массивов переменной длины. Еслиaиbимеют фиксированные типы, вы можете написать вswitchкод, который подготавливает указатели на них на основе перечисления, а затем использовать эти указатели для присвоения им значений.