I'm trying to define a macro to return the result of a range check which works on signed and unsigned types. However, because I am compiling with -Wextra
which includes -Wtype-limits
, I want to ignore -Wtype-limits
just for this macro. Unfortunately, this:
#define IN_RANGE(v, min, max) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
( (((v) < (max)) && ((v) > (min))) ? true : false ) \
_Pragma("GCC diagnostic pop")
Fails to compile (because I'm also using -Werror
) with:
... error: expected expression before '#pragma'
[build] 40 | _Pragma("GCC diagnostic push") \
[build] | ^~~~~~~
Edit: here's a complete example.
#include <stdio.h>
#include <stdint.h>
#define IN_RANGE(v, min, max) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
( (((v) < (max)) && ((v) > (min))) ? true : false ) \
_Pragma("GCC diagnostic pop")
const uint32_t MIN_NUM = 0;
const uint32_t MAX_NUM = 10;
int main() {
printf("%s", IN_RANGE(8, MIN_NUM, MAX_NUM) ? "OK": "FAIL");
return 0;
}
Fails with (on gcc 9):
$ gcc -Wall -Wextra example.c
example.c: In function ‘main’:
example.c:14:1: error: expected expression before ‘#pragma’
14 | printf("%s", IN_RANGE(8, MIN_NUM, MAX_NUM) ? "OK": "FAIL");
| ^ ~
Given some experimentation, it seems that you can't just embed the _Pragma()
operator in the middle of an expression. In effect, it needs to be used where a statement can be used.
This code compiles, and AFAICT it is because there's a complete statement sandwiched between the _Pragma()
operators:
#include <stdbool.h>
#include <stdio.h>
#define IN_RANGE2(r, v, min, max) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
((r) = (((v) < (max)) && ((v) > (min)))); \
_Pragma("GCC diagnostic pop")
int main(void)
{
int x = 10;
bool y;
IN_RANGE2(y, x, -9, +9);
printf("%d\n", y);
return 0;
}
Note the semicolon before the third _Pragma()
operator. Without that, I was getting expected ‘;’ before ‘#pragma’
(as an error since I too compile with -Werror
).
I've also simplified the conditional so it doesn't use the ternary operator, as I noted in a comment.
The relevant section of the standard is §6.10.9 Pragma operator. It says:
A unary operator expression of the form:
_Pragma ( string-literal )
is processed as follows: The string literal is destringized by deleting any encoding prefix, deleting the leading and trailing double-quotes, replacing each escape sequence
\"
by a double-quote, and replacing each escape sequence\\
by a single backslash. The resulting sequence of characters is processed through translation phase 3 to produce preprocessing tokens that are executed as if they were the pp-tokens in a pragma directive. The original four preprocessing tokens in the unary operator expression are removed.
Think about what the code expands to in your example:
int main(void)
{
printf("%s", IN_RANGE(8, MIN_NUM, MAX_NUM) ? "OK": "FAIL");
return 0;
}
is approximately equivalent to:
/* SO 7548-0114 */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
const uint32_t MIN_NUM = 0;
const uint32_t MAX_NUM = 10;
int main(void)
{
printf("%s",
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
((((8) < (MAX_NUM)) || (8) > (MIN_NUM)) ? true : false)
#pragma GCC diagnostic push
? "OK": "FAIL");
return 0;
}
This doesn't compile. It produces the error:
pragma67.c: In function ‘main’:
pragma67.c:12:11: error: expected expression before ‘#pragma’
12 | #pragma GCC diagnostic push
| ^~~
Line 12 is the first #pragma
directive.
I'm not totally convinced that this is what the standard mandates, but it is what I get from GCC 11.2.0.
However, when I compile with Apple's Clang (Apple clang version 14.0.0 (clang-1400.0.29.202)
), both versions of the code I show compile OK. Thus, there is a discrepancy in the interpretation of the standard between these two major C compilers.
And, indeed, when I compile with Clang, your original code compiles cleanly. You can probably make a case out for a bug report to the GCC team. However, if you're going to write portable code, you'll need to accept the limitations imposed by GCC for a few years yet (though that depends on your portability requirements).