Название этого вопроса звучит немного странно, но я не мог придумать лучшего способа его сформулировать. Моя проблема в следующем; У меня есть тип внутри проекта под названием AmbiguousType, который представляет собой объединение в этом формате.
Declarations.h (заголовочный файл утилиты)
typedef union AmbiguousType
{
u32 unsigned32;
u64 unsigned64;
i32 signed32;
i64 signed64;
} AmbiguousType;
Теперь у меня есть много вспомогательных функций, которые облегчают использование этого типа. Я прокручивал эти функции, пытаясь найти способы сделать их более эффективными/менее громоздкими, и кое-что понял; большинство функций имеют оператор switch в качестве основного тела. Возьмем, к примеру, эту функцию, которая используется для присвоения заданному неоднозначному типу.
Declarations.c (исходный файл утилиты)
/**
* @brief Assign a value to an ambiguous type.
* @param affected The affected variable.
* @param member What state are we making the ambiguous type?
* @param value The value that state will have.
*/
void AssignAmbiguousType(AmbiguousType* affected, AmbiguousTypeSpecifier member,
void* value)
{
switch (member)
{
// Note that "VPTT" is a helper macro to turn a void pointer into the given type.
// U__ - unsigned __ bit integer.
// I__ - signed __ bit integer.
case unsigned32: affected->unsigned32 = VPTT(u32, value); return;
case unsigned64: affected->unsigned64 = VPTT(u64, value); return;
case signed32: affected->signed32 = VPTT(i32, value); return;
case signed64: affected->signed64 = VPTT(i64, value); return;
}
}
Мой вопрос: существует ли прагматичный способ удалить этот оператор switch из функции, сохранив при этом его использование? Мне это кажется излишним, но я не могу понять, что могло бы решить эту проблему.
Я пробовал использовать макросы для простого объединения токенов (##), но это явно не сработало, поскольку параметры известны только во время выполнения. Однако здесь я в растерянности. Есть ли вообще решение этой проблемы, или это лучшее, что можно получить?
@Zenais, сделайте AmbiguousType членом AmbiguousStruct, у которого есть второй член, который является указателем на набор функций, которые будут использоваться с этим экземпляром AmbiguousStruct.
@chux-ReinstateMonica Виртуальная таблица на C? ... в некоторых частях мира это ересь (хотя более серьезно; как насчет присвоения старшего бита u64 в качестве тега типа, при условии, что ОП согласен с его сокращением до 56-битного целого числа?)
@Dai, vtable правда, но звучит весело, когда крутишь лампочку на C++. (или свет C++ — это оксюморон?)
@Dai, Note OP нужно только 2 бита, поэтому uint62_t, но этот подход (2 бита или байт) все еще не избавляет от switch().
Смотрите мой ответ: Написание «общего» метода struct-print на C Он показывает подход switch и то, как преобразовать его в vtable. Это может дать вам некоторые идеи.
Ваше описание проблемы не указывает на необходимость иметь тип AmbiguousType. Просто используйте тип, которого будет достаточно для представления всех ваших случаев. И тогда не будет никаких операторов переключения/регистра.





Вы можете уменьшить повторяемость, используя макросы, подобные X-макросам. Вот пример.
// define once, include everywhere
#define GEN_SWITCH(DISCRIMINANT, BODY) \
switch(DISCRIMINANT) { \
case DISC(u,32) : BODY(u,32) break; \
case DISC(i,32) : BODY(i,32) break; \
case DISC(u,64) : BODY(u,64) break; \
case DISC(i,64) : BODY(i,64) break; \
default: cannot_happen(); }
#define CONCAT(a,b) a ## b
#define CONCAT2(a,b) CONCAT(a,b)
#define TYPENAME_i(sz) CONCAT2(signed,sz)
#define TYPENAME_u(sz) CONCAT2(unsigned,sz)
#define VPNAME(ui,sz) CONCAT(ui,sz)
#define FIELD(ui,sz) CONCAT2(TYPENAME_,ui)(sz)
#define DISC(ui,sz) CONCAT2(TYPENAME_,ui)(sz)
// more macros that map signedness+size to something
// When you need to generate a switch
#define BODY(ui,sz) affected->FIELD(ui,sz) = VPTT(VPNAME(ui,sz), value);
GEN_SWITCH(member, BODY)
Последняя строка расширяется до следующего (отформатировано для удобства чтения):
switch(member) {
case unsigned32 : affected->unsigned32 = VPTT(u32, value); break;
case signed32 : affected->signed32 = VPTT(i32, value); break;
case unsigned64 : affected->unsigned64 = VPTT(u64, value); break;
case signed64 : affected->signed64 = VPTT(i64, value); break;
default: cannot_happen();
}
Таким образом, каждая вспомогательная функция будет выглядеть так:
#define BODY ... something ...
GEN_SWITCH(discriminant, BODY)
Прошу прощения за поздний ответ, был в отпуске. В любом случае, огромное вам за это спасибо! Я не знаю, как я не понял, что нужно сделать это раньше, это удовлетворяет всем моим критериям (то есть красивее) и при этом не требует каких-либо затрат на производительность. Спасибо.
Предполагая, что ваша ISA имеет прямой порядок байтов, и если вы не возражаете против некоторых носовых демонов, то вы можете просто полностью отказаться от типа
union: просто передать одно значениеu64, потому что оно может быть источником операций чтения с меньшим типом. значения и цель записи в меньшие целочисленные типы.