Типовое программирование с помощью макросов: приемы определения типа? - PullRequest
8 голосов
/ 02 декабря 2010

В макросах C можно выполнять определенные типы типов-обобщенных функций, например, такие вещи, как:

#define SQRT(x) (sizeof(x) == sizeof(float) ? sqrtf((x)) : \
                 sizeof(x) == sizeof(double) ? sqrt((x)) : \
                 sqrtl((x)) )

.x - это тип с плавающей точкой.

Но что, если я хочу макрос общего типа, который может принимать либо целочисленный тип, либо тип указателя, который может иметь одинаковый размер.Есть ли умный способ проверить, является ли аргумент макроса целым числом или указателем?А как насчет целого числа по сравнению с типом с плавающей точкой?

Ответы [ 5 ]

3 голосов
/ 02 декабря 2010

Вы можете определить, является ли выражение целочисленным выражением или выражением char*, по крайней мере, в архитектурах, где приведение от указателя к uintptr_t хорошо определено:

#define INT_OR_CHARP(X) (((uintptr_t)((X)+1) - (uintptr_t)(X)) == 1)

Это обнаружит, если X - указатель на тип T с sizeof(T) > 1.Это не будет работать для void* и других угловых случаев.И поскольку X оценивается два раза, вам придется следить за побочными эффектами.

Чтобы избежать проблем с целочисленным переполнением, если X имеет тип signed int, вы можете заменить (X) на

(1 ? (X) : (uintmax_t)0)

Это гарантирует, что если X является целочисленным выражением, оно будет иметь тип uintmax_t.Возможно, значение +1 обернется, но результат всегда будет четко определен, а разница между двумя частями всегда будет 1.Если X является выражением указателя, то это так, потому что любое константное целочисленное выражение значения 0 также является константой нулевого указателя .

Всего это дает

#define INT_OR_CHARP(X) (((uintptr_t)((1 ? (X) : (uintmax_t)0)+1) - (uintptr_t)(1 ? (X) : (uintmax_t)0)) == 1)
3 голосов
/ 02 декабря 2010

Нет. Макросы не знают, что это за типы. Они выполняют буквальное копирование и вставку #define. Безопасность типов здесь просто не существует.

C не является строго типизированным языком в каком-либо значимом смысле. Если вы хотите хоть немного безопасности типов, используйте C ++, где вы можете сделать немного с помощью шаблонов и перегрузки функций.

2 голосов
/ 02 декабря 2010

Ваш результат не на самом деле тип-общий, потому что результат всегда long double независимо от того, какой тип аргумента передается - тип результата ?:, когда второй и третий операнды арифметические типы - это тип, который получился бы в результате применения обычных арифметических преобразований к этим операндам. Чтобы сделать это, вы можете использовать расширение GCC typeof:

#define SQRT(x) (__typeof__ (x))(sizeof(x) == sizeof(float) ? sqrtf((x)) : \
                 sizeof(x) == sizeof(double) ? sqrt((x)) : \
                 sqrtl((x)) )

Целое число с плавающей точкой также можно сделать с помощью typeof:

(__typeof__ (X))1.1 == 1

Я не могу придумать, как сделать целочисленный-против-указатель. Методы, описанные на этой странице , весьма интересны.

1 голос
/ 29 августа 2017

Для этой цели в стандарт C11 было добавлено ключевое слово _Generic.

Это работает как оператор переключения для типов выражений.

Ваш пример может бытьнаписано с использованием этого ключевого слова, например:

#define SQRT(X) _Generic((X),
    float: sqrtf, \
    double: sqrt, \
    default: sqrtl \
)(X)

GCC обеспечивает поддержку этого ключевого слова, начиная с версии 4.9 .

0 голосов
/ 02 декабря 2010

Можно иметь какую-то систему проверки типов, но это действительно клочок в C.

glib делает это; Вы можете взглянуть на то, как они это делают, или, возможно, использовать это сами (это в любом случае отличная библиотека C).

...