Стандарт C17 требует, чтобы в выражении _Generic:
Никакие две родовые ассоциации в одной и той же родовой селекции не должны укажите совместимые типы. Тип управляющего выражения: тип выражения, как если бы оно подверглось преобразованию lvalue [....]
@Davilsor в этом посте: https://codereview.stackexchange.com/a/289337/265278 утверждает, что:
_Genericна самом деле предназначен только для того, чтобы сделать реализацию<math.h>более переносимой, поэтому я не полностью доверяю ему целочисленные типы. Я бы особенно опасался расширенияcharиshort intдоint.
Если uint64_t — это typedef, скажем, unsigned long long int, и они оба у меня есть в каком-то выражении _Generic, будет ли это противоречить «Никакие две универсальные ассоциации в одном и том же общем выборе не должны указывать совместимые типы»? Если да, то какого поведения мне следует ожидать? Компиляция не удалась? Неопределенное поведение? Диагностика?
Возьмем это, например:
#define map(count, iter, func) _Generic (*iter, \
char: map_c, \
unsigned char: map_uc, \
short int: map_si, \
unsigned short int: map_usi, \
int: map_i, \
unsigned int: map_ui, \
long int: map_li, \
unsigned long int: map_uli, \
long long int: map_lli, \
unsigned long long int: map_ulli, \
float: map_f, \
double: map_d, \
long double: map_ld, \
_Bool: map_b, \
char *: map_s \
)(count, iter, func)
Есть ли что-нибудь, что потенциально может пойти не так в приведенном выше выражении _Generic, особенно если я добавлю к нему целочисленные типы фиксированной ширины из stdint.h?
@JonathanLeffler В «doc/generic11.c» вашего репозитория «soq» вы использовали (x) + (y) в качестве управляющего выражения. Но не будет ли это иметь неопределенное поведение для типов signed при переполнении? Если да, то есть ли способ избежать этого?
Да, как и почти в любом выражении, включающем целочисленные типы со знаком, существует риск переполнения. Вы справляетесь с этим так же, как с риском с помощью int z = x+ y; — вы можете игнорировать его, использовать проверяемые арифметические функции из C23 или выполнять другие проверки, если считаете это целесообразным.
@JonathanLeffler Знаете ли вы, реализованы ли «проверенные арифметические функции» C23 какой-либо стандартной библиотекой? Я не видел компилятора, поддерживающего их.
@Lundin gcc.gnu.org/gcc-14/changes.html говорит, что GCC 14 реализовал <stdckdint.h>.
@Lundin Releases.llvm.org/18.1.0/tools/clang/docs/ReleaseNotes.html говорит, что в Clang 18.1 также реализован <stdckdint.h>.
@Джонатан Я построил ваш общий макрос и в итоге получил тот, который работает для всех целочисленных типов (включая _Bool), работает со смешанными знаковыми и беззнаковыми типами и не имеет целочисленного переполнения. Думаю, вам будет интересно это увидеть: codereview.stackexchange.com/questions/292409/…
Обратите внимание, что §6.5.1.1 Общий выбор ¶3 гласит: Управляющее выражение общего выбора не оценивается. Поскольку оно не оценивается, оно не может переполниться. Это имеет смысл. Управляющее выражение определяет тип во время компиляции; переполнение происходит во время выполнения.
@JonathanLeffler Понятно, так что добавление никогда не было проблемой. _Generic имеет много правил, но мне было очень полезно. Спасибо.
Поскольку мое имя проверено, я узнал несколько хороших ответов. C17 изменил формулировку о том, какие преобразования претерпевает тип управляющего выражения, и я не был в курсе.





Если
uint64_tявляется определением типа, скажем,unsigned long long int, и они оба у меня есть в каком-то выражении_Generic, будет ли это противоречить «Никакие две универсальные ассоциации в одном и том же общем выборе не должны указывать совместимые типы»?
Да.
Если да, то какого поведения мне следует ожидать? Компиляция не удалась? Неопределенное поведение? Диагностика?
Появится диагностическое сообщение, и большинство компиляторов откажутся компилировать программу.
Есть ли что-то, что потенциально может пойти не так в приведенном выше выражении
_Generic,
Нет, не так, как сейчас написано.
особенно если я добавлю к нему целочисленные типы фиксированной ширины из
stdint.h?
Да, тогда у вас возникнут проблемы, поскольку вы укажете один и тот же тип более одного раза, и вы получите диагностические сообщения, и программа, скорее всего, не сможет скомпилироваться.
«у вас возникнут проблемы, поскольку тогда вы будете указывать один и тот же тип более одного раза». Если мы говорим о типах intn_t/uintn_t - так называемых фиксированной ширины - тогда они по определению не получат один и тот же тип дважды. Все типы от int8_t до int64_t будут уникальными типами. Так что все будет работать нормально. Но в случае int_leastn_t/int_fastn_t несколько типов в списке ассоциаций _Generic могут конфликтовать.
@Lundin OP спрашивает, что произойдет при добавлении типов фиксированной ширины в список, который уже охватывает основные типы.
Ах, ладно, справедливо, да, тогда определенно будут столкновения.
@Тед Люнгмо хорошо отвечает на явные вопросы ОП.
Тем не менее, остается без ответа подразумеваемый вопрос: как использовать _Generic для управления кодом, когда uint64_t не соответствует ни одному стандартному типу?
Используйте больше уровней
На верхнем уровне map() добавьте default:, который управляет кодом между uint64_t, uint32_t и т. д. Это можно даже перенести на дополнительные уровни для size_t, uintmax_t, которые могут конфликтовать с предыдущими типами.
#include <stdint.h>
#include <stdio.h>
int map_uc(int, unsigned char*, int);
int map_usi(int, unsigned short*, int);
int map_ui(int, unsigned*, int);
int map_uli(int, unsigned long*, int);
int map_ulli(int, unsigned long long*, int);
int map_u8(int, uint8_t*, int);
int map_u16(int, uint16_t*, int);
int map_u32(int, uint32_t*, int);
int map_u64(int, uint64_t*, int);
#define map2(iter) _Generic (*(iter), \
uint8_t: map_u8, \
uint16_t: map_u16, \
uint32_t: map_u32, \
uint64_t: map_u64 \
)
#define map(count, iter, func) _Generic (*(iter), \
unsigned char: map_uc, \
unsigned short int: map_usi, \
unsigned int: map_ui, \
unsigned long int: map_uli, \
unsigned long long int: map_ulli, \
default: map2(iter) \
) ((count), (iter), (func))
int main(void) {
uint64_t sz = 123;
uint64_t *xx = &sz;
printf("%d\n", map(0, xx, 0));
return 0;
}
Что касается целочисленных типов, _Generic и stdint.h, похоже, существует несколько заблуждений.
_Generic не продвигает по типу первый элемент в списке ассоциаций (но проводит его через «преобразование lvalue», что означает отбрасывание квалификаторов типа).
Однако имейте в виду, что если вы передадите ему выражение, то это
выражение само по себе может содержать неявные повышения типа. Например
_Generic((signed char){}, ... приведет к signed char но
_Generic(+(signed char){}, ... приведет к int. Что, конечно, не имеет ничего общего с _Generic само по себе.
Для стандартных «примитивных» целочисленных типов данных C все они будут уникальными типами по отношению друг к другу: signed char, short, int, long и long long. Несмотря на то, что некоторые из них могут иметь одинаковый размер, они будут иметь разный рейтинг конверсии, поэтому _Generic следует рассматривать их все как отдельные типы.
Для типов stdint.h они будут реализованы как typedef, соответствующие «примитивным» типам данных (C17 7.20 §4), что означает, что вы не можете иметь, например, int и int16_t в одном и том же списке _Generic ассоциаций, при переносимости. Они могут быть или не быть одного и того же типа.
Любой компилятор C обязательно предоставляет int8_t, int16_t, int32_t и int64_t, если соответствующий тип существует на аппаратном уровне целевой системы. Это типы точной ширины, поэтому все они являются уникальными типами по отношению друг к другу, учитывая, что тип существует в конкретной системе.
Однако обязательные типы int_leastn_t и int_fastn_t могут быть дубликатами друг друга.
Краткое содержание:
Хорошо определяемый и 100% портативный без поднятой диагностики:
_Generic((x),
signed char: 0,
short: 0,
int: 0,
long: 0,
long long: 0)
_Generic((x),
int8_t: 0,
int16_t: 0,
int32_t: 0,
int64_t: 0)
(Хотя малоизвестные/экзотические системы не обязательно должны предоставлять все типы точной ширины stdint.h.)
Существует три типа
char, два из которых имеют одинаковый диапазон. Но это имеет отношение к вашему вопросу.