如何在C中使用静态断言来检查传递给宏的参数的类型 [英] How to use static assert in C to check the types of parameters passed to a macro

查看:77
本文介绍了如何在C中使用静态断言来检查传递给宏的参数的类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要编写一个C宏来检查以确保传递给它的所有参数都是unsigned并且具有相同的整数类型.例如:所有输入参数均为uint8_t或全部uint16_t或全部uint32_t或全部uint64_t.

I need to write a C macro that checks to ensure all parameters passed to it are unsigned and of the same integer type. Ex: all input params are uint8_t, or all uint16_t, or all uint32_t, or all uint64_t.

这是在C ++中可以进行这种检查的方式:

Here is how this type of checking can be done in C++: Use static_assert to check types passed to macro

即使仅通过gcc扩展,在C中是否也存在类似的东西?

Does something similar exist in C, even if only by way of a gcc extension?

请注意,静态断言可通过_Static_assert在gcc中使用. (请在此处查看我的答案: C中的静态断言.)

Note that static asserts are available in gcc via _Static_assert. (See my answer here: Static 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 .


UPDATE:

Here's precisely how to do what I want in C++ (using a function template, static_assert, and the <type_traits> header file). I needed to learn this anyway, for comparison purposes, so I just did. Run this code for yourself here: 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进行注释,由于输入不是未签名的,因此您会在静态断言中遇到此失败:

With just TEST A uncommented, you get this failure in the static assert since the inputs aren't unsigned:

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 ||
     ^~~~~~~~~~~~~

仅对测试B进行注释,您会因为模板期望所有输入具有相同的T类型而无法从模板中找到有效的函数:

with just TEST B uncommented, you get this failure to find a valid function from the template since the template expects all inputs to be the same type 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);
                             ^

在没有注释TEST C的情况下,它通过并看起来像这样!

And with just TEST C uncommented, it passes and looks like this!

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. 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. How do I restrict a template class to certain built-in types?

相关:

  1. 使用static_assert检查传递给宏 [我自己的答案]
  2. C语言中的静态断言 [我自己的回答]
  1. Use static_assert to check types passed to macro [my own answer]
  2. Static assert in C [my own answer]

推荐答案

如果最重要的方面是如果ab是不同类型,则希望它无法编译,则可以使用C11的_Generic以及GCC的__typeof__扩展名来进行管理.

If the most important aspect here is that you want it to fail to compile if a and b are different types, you can make use of C11's _Generic along with GCC's __typeof__ extension to manage this.

一个通用示例:

#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);
}

现在,如果我们尝试编译此代码,它将可以正常编译,并且每个人都很高兴.

Now if we try to compile this code, it will compile fine and everybody is happy.

但是,如果将b的类型更改为unsigned int,它将无法编译.

If we change the type of b to unsigned int, however, it will fail to compile.

之所以起作用,是因为_Generic选择使用控制表达式的类型(在这种情况下为(Y))来选择要遵循的规则并插入与该规则相对应的代码.在这种情况下,我们仅提供了__typeof__(X)的规则,因此,如果(X)不是(Y)的兼容类型,则没有合适的规则可供选择,因此无法编译.为了处理具有控制表达式的数组,该表达式将衰减为指针,我添加了另一个_Generic,该数组以另一种方式确保它们必须彼此兼容,而不是接受单向兼容性.而且,就我所关心的而言,我们只想确保它在不匹配时无法编译,而不是在匹配时执行某些特定的操作,因此我给相应的规则指定了什么都不做的任务:(void)NULL

This works because _Generic selection uses the type of a controlling expression ((Y) in this case) to select a rule to follow and insert code corresponding to the rule. In this case, we only provided a rule for __typeof__(X), thus if (X) is not a compatible type for (Y), there is no suitable rule to select and therefore cannot compile. To handle arrays, which have a controlling expression that will decay to a pointer, I added another _Generic that goes the other way ensuring they must both be compatible with one another rather than accepting one-way compatibility. And since--as far as I particularly cared--we only wanted to make sure it would fail to compile on a mismatch, rather than execute something particular upon a match, I gave the corresponding rule the task of doing nothing: (void)NULL

在这种情况下,这种技术绊倒了:_Generic不能处理可变可修改类型,因为它是在编译时处理的.因此,如果您尝试使用可变长度数组来执行此操作,它将无法编译.

There is a corner case where this technique stumbles: _Generic does not handle Variably Modifiable types since it is handled at compile time. So if you attempt to do this with a Variable Length Array, it will fail to compile.

要处理固定宽度无符号类型的特定用例,我们可以修改嵌套的_Generic来处理它,而不是处理数组的特殊性:

To handle your specific use-case for fixed-width unsigned types, we can modify the nested _Generic to handle that rather than handling the pecularities of an array:

#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 \
   ) \
)

传递不兼容类型时的示例GCC错误:

Example GCC error when passing non-compatible types:

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), \
      |                                  ^

值得一提的是,作为ccc扩展的__typeof__并不是一个可移植到所有编译器的解决方案.不过,它似乎确实可以与Clang一起使用,因此这是另一个支持它的主要编译器.

It is worth mentioning that __typeof__, being a GCC extension, will not be a solution that is portable to all compilers. It does seem to work with Clang, though, so that's another major compiler supporting it.

这篇关于如何在C中使用静态断言来检查传递给宏的参数的类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆