Как использовать stati c assert в C для проверки типов параметров, передаваемых макросу - PullRequest
1 голос
/ 10 марта 2020

Мне нужно написать макрос C, который проверяет, чтобы все передаваемые ему параметры были unsigned и имели одинаковый целочисленный тип. Пример: все входные параметры uint8_t, или все uint16_t, или все uint32_t, или все uint64_t.

Вот как этот тип проверки может быть выполнен в C ++: Используйте static_assert для проверки типов, передаваемых в макрос

Существует ли нечто подобное в C, даже если только через расширение ag cc?

Обратите внимание, что утверждения stati c доступны в g cc через _Static_assert. (См. Мой ответ здесь: Stati c assert in C).

Это не работает:

int a = 1; 
int b = 2;
_Static_assert(__typeof__ a == __typeof__ b, "types don't match");

Ошибка:

main.c: In function ‘main’:
main.c:23:20: error: expected expression before ‘__typeof__’
     _Static_assert(__typeof__ a == __typeof__ b, "types don't match");

ОБНОВЛЕНИЕ:

Вот как именно то, что я хочу сделать в C ++ (используя шаблон функции , static_assert и <type_traits> заголовочный файл ). В любом случае, мне нужно было изучить это в целях сравнения, поэтому я просто сделал это. Запустите этот код для себя здесь: https://onlinegdb.com/r1k-L3HSL.

#include <stdint.h>
#include <stdio.h>
#include <type_traits> // std::is_same()

// Templates: https://www.tutorialspoint.com/cplusplus/cpp_templates.htm

// Goal: test the inputs to a "C macro" (Templated function in this case in C++) to ensure
// they are 1) all the same type, and 2) an unsigned integer type

// 1. This template forces all input parameters to be of the *exact same type*, even 
//    though that type isn't fixed to one type! This is because all 4 inputs to test_func()
//    are of type `T`.
template <typename T>
void test_func(T a, T b, T c, T d)
{
    printf("test_func: a = %u; b = %u; c = %u; d = %u\n", a, b, c, d);

    // 2. The 2nd half of the check: 
    // check to see if the type being passed in is uint8_t OR uint16_t OR uint32_t OR uint64_t!
    static_assert(std::is_same<decltype(a), uint8_t>::value ||
                  std::is_same<decltype(a), uint16_t>::value ||
                  std::is_same<decltype(a), uint32_t>::value ||
                  std::is_same<decltype(a), uint64_t>::value,
                  "This code expects the type to be an unsigned integer type\n"
                  "only (uint8_t, uint16_t, uint32_t, or uint64_t).");

    // EVEN BETTER, DO THIS FOR THE static_assert INSTEAD!
    // IE: USE THE TEMPLATE TYPE `T` DIRECTLY!
    static_assert(std::is_same<T, uint8_t>::value ||
                  std::is_same<T, uint16_t>::value ||
                  std::is_same<T, uint32_t>::value ||
                  std::is_same<T, uint64_t>::value,
                  "This code expects the type to be an unsigned integer type\n"
                  "only (uint8_t, uint16_t, uint32_t, or uint64_t).");
}

int main()
{
    printf("Begin\n");

    // TEST A: This FAILS the static assert since they aren't unsigned 
    int i1 = 10;
    test_func(i1, i1, i1, i1); 

    // TEST B: This FAILS to find a valid function from the template since 
    // they aren't all the same type 
    uint8_t i2 = 11;
    uint8_t i3 = 12;
    uint32_t i4 = 13;
    uint32_t i5 = 14;
    test_func(i2, i3, i4, i5);

    // TEST C: this works!
    uint16_t i6 = 15;
    uint16_t i7 = 16;
    uint16_t i8 = 17;
    uint16_t i9 = 18;
    test_func(i6, i7, i8, i9);

    return 0;
}

Только с комментарием TEST A вы получите этот сбой в утверждении stati c, так как входные данные не подписаны :

main.cpp: In instantiation of ‘void test_func(T, T, T, T) [with T = int]’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',46)">main.cpp:46:29</span>:   required from here
main.cpp:32:5: error: static assertion failed: This code expects the type to be an unsigned integer type
only (uint8_t, uint16_t, uint32_t, or uint64_t).
     static_assert(std::is_same<decltype(a), uint8_t>::value ||
     ^~~~~~~~~~~~~

только с комментарием TEST B вы получаете эту ошибку, чтобы найти действительную функцию из шаблона, так как шаблон ожидает, что все входные данные будут одного типа T:

main.cpp: In function ‘int main()’:
main.cpp:54:29: error: no matching function for call to ‘test_func(uint8_t&, uint8_t&, uint32_t&, uint32_t&)’
     test_func(i2, i3, i4, i5);
                             ^
main.cpp:26:6: note: candidate: template void test_func(T, T, T, T)
 void test_func(T a, T b, T c, T d)
      ^~~~~~~~~
main.cpp:26:6: note:   template argument deduction/substitution failed:
main.cpp:54:29: note:   deduced conflicting types for parameter ‘T’ (‘unsigned char’ and ‘unsigned int’)
     test_func(i2, i3, i4, i5);
                             ^

И только с ТЕСТ C без комментариев, он проходит и выглядит так!

Begin
test_func: a = 15; b = 16; c = 17; d = 18

Ссылки:

  1. http://www.cplusplus.com/reference/type_traits/is_same/
  2. https://en.cppreference.com/w/cpp/types/is_same
  3. https://en.cppreference.com/w/cpp/language/decltype
  4. Как ограничить класс шаблона некоторыми встроенными типами?

Связанные:

  1. Используйте static_assert для проверки типов, передаваемых в макрос [мой собственный ответ]
  2. Stati c утверждают в C [мой собственный ответ]

Ответы [ 2 ]

2 голосов
/ 10 марта 2020

Если наиболее важным аспектом здесь является то, что вы хотите, чтобы он не компилировался, если a и b - это разные типы, вы можете использовать _Generic в C11 вместе с __typeof__ в G CC. расширение для управления этим.

Пример generi c:

#include <stdio.h>

#define TYPE_ASSERT(X,Y) _Generic ((Y), \
    __typeof__(X): _Generic ((X), \
        __typeof__(Y): (void)NULL \
    ) \
)

int main(void)
{
    int a = 1; 
    int b = 2;
    TYPE_ASSERT(a,b);
    printf("a = %d, b = %d\n", a, b);
}

Теперь, если мы попытаемся скомпилировать этот код, он скомпилируется нормально, и все будут довольны.

Однако если мы изменим тип b на unsigned int, он не будет скомпилирован.

Это работает, потому что при выборе _Generic используется тип управляющего выражения (в данном случае (Y)). ) выбрать правило, которому нужно следовать, и вставить код, соответствующий правилу. В этом случае мы предоставили только правило для __typeof__(X), поэтому, если (X) не является совместимым типом для (Y), подходящего правила для выбора нет и, следовательно, он не может быть скомпилирован. Для обработки массивов, которые имеют управляющее выражение, которое будет распадаться на указатель, я добавил еще один _Generic, который идет по-другому, гарантируя, что они оба должны быть совместимы друг с другом, а не принимать одностороннюю совместимость. И поскольку - насколько мне было особенно важно - мы только хотели убедиться, что он не будет компилироваться при несовпадении, а не выполнять что-то конкретное при совпадении, я дал соответствующему правилу задачу ничего не делать: (void)NULL

Существует угловой случай, когда эта техника запинается: _Generic не обрабатывает переменно изменяемые типы, поскольку он обрабатывается во время компиляции. Поэтому, если вы попытаетесь сделать это с массивом переменной длины, он не сможет скомпилироваться.

Чтобы обработать ваш указанный c сценарий использования для типов без знака фиксированной ширины, мы можем изменить вложенный _Generic, чтобы обрабатывать это, а не обрабатывать особенности массива:

#define TYPE_ASSERT(X,Y) _Generic ((Y), \
    __typeof__(X): _Generic ((Y), \
        uint8_t: (void)NULL, \
        uint16_t: (void)NULL, \
        uint32_t: (void)NULL, \
        uint64_t: (void)NULL \
   ) \
)

Пример G CC ошибка при передаче несовместимых типов:

main.c: In function 'main':
main.c:7:34: error: '_Generic' selector of type 'signed char' is not compatible with any association
    7 |         __typeof__(X): _Generic ((Y), \
      |                                  ^

Стоит отметить, что __typeof__, являясь расширением G CC, не будет решением, переносимым на все компиляторы. Похоже, он работает с Clang, так что это еще один важный компилятор, поддерживающий его.

1 голос
/ 20 марта 2020

То, что вы хотите, выполнимо в стандартном C11, никаких расширений или G CC не требуется.

Мы доработаем до окончательного ответа, так что все могут следовать.


Согласно стандарту C11 [6.7.10], static_assert-декларация: _Static_assert( constant-expression , string-literal ) является декларацией . Таким образом, если мы собираемся использовать макрос, нам лучше всего предоставить scope для объявления, чтобы сохранить порядок. Обычно в обычной форме:

#define MY_AMAZING_MACRO() do {_Static_assert(...some magic...);} while(0)

Далее, чтобы наша _Static_assert в макросе по крайней мере повторяла через stdio фактическую проблему, если утверждение не удалось, хорошо используйте знакомую настройку строкового преобразования:

#define STATIC_ASSERT_H(x)  _Static_assert(x, #x)
#define STATIC_ASSERT(x)    STATIC_ASSERT_H(x)

Далее мы будем использовать функцию выбора Generi c в C11, чтобы объявить макрос, который возвращает константу 1, если объект того типа, который мы ищем, и ноль в противном случае:

#define OBJ_IS_OF_TYPE(Type, Obj) _Generic(Obj, Type: 1, default: 0)

Далее мы сделаем макрос, чтобы проверить, все ли ваши входы имеют одинаковый тип :

#define ALL_OBJS_ARE_OF_TYPE(Type, Obj_0, Obj_1, Obj_2, Obj_3)  \
    (OBJ_IS_OF_TYPE(Type, Obj_0) &&                             \
     OBJ_IS_OF_TYPE(Type, Obj_1) &&                             \
     OBJ_IS_OF_TYPE(Type, Obj_2) &&                             \
     OBJ_IS_OF_TYPE(Type, Obj_3))

Далее Используя вышеизложенное, сделайте макрос, чтобы проверить, все ли из ваших входов далее один из четырех типов :

#define IS_ACCEPTABLE(Type_0, Type_1, Type_2, Type_3, Obj_0, Obj_1, Obj_2, Obj_3)   \
    (ALL_OBJS_ARE_OF_TYPE(Type_0, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
     ALL_OBJS_ARE_OF_TYPE(Type_1, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
     ALL_OBJS_ARE_OF_TYPE(Type_2, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
     ALL_OBJS_ARE_OF_TYPE(Type_3, Obj_0, Obj_1, Obj_2, Obj_3))

И, наконец, сложив все вместе :

#define TEST_FUNC(a,b,c,d)                                              \
do                                                                      \
{                                                                       \
    STATIC_ASSERT(IS_ACCEPTABLE(uint8_t, uint16_t, uint32_t, uint64_t,  \
                                a,       b,        c,        d));       \
} while(0)

Конечно, вы можете разделить вышеперечисленное на более отдельные, отдельные STATIC_ASSERT, как вы будете sh, если вы хотите выводить больше подробных ошибок, если какой-либо из _Static_assert s потерпеть неудачу.

...