Verify macro argument size at compilation time

_static_assert
c compile time assert
gcc assert
visual studio static_assert

Let's assume I have a macro (more details about why, is below in the P.S. section)

void my_macro_impl(uint32_t arg0, uint32_t arg1, uint32_t arg2);

...

#define MY_MACRO(arg0, arg1, arg2)       my_macro_impl((uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2))

The HW on which this macro is going to be used is little endian and uses 32bit architecture so that all the pointers are up to (and including) 32 bit width. My goal is to warn the user when it passes uint64_t or int64_t parameter by mistake.

I was thinking about using sizeof like this

#define MY_MACRO(arg0, arg1, arg2)       do \
                                         {  \
                                             static_assert(sizeof(arg0) <= sizeof(uint32_t));  \
                                             static_assert(sizeof(arg1) <= sizeof(uint32_t));  \
                                             static_assert(sizeof(arg2) <= sizeof(uint32_t));  \
                                             my_macro_impl((uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2));  \
                                         } while (0)

But the user can use MY_MACRO with a bit-field and then my code fails to compile:

error: invalid application of 'sizeof' to bit-field

Question: Is there an option to detect at the compilation time if the size of the macro argument larger than, let's say, uint32_t?


P.S.

The MY_MACRO is going to act similarly to printf in a real-time embedded environment. This environment has a HW logger which may receive up to 5 parameters, each parameter should be 32 bits. The goal is to preserve the standard format as for printf. The format strings are parsed offline and the parser is well aware that every parameter is 32 bits, so it will cast it based on the %... from the format string. Possible usages are below.

Not desired usage:

uint64_t time = systime_get();
MY_MACRO_2("Starting execution at systime %llx", time); // WRONG! only the low 32 bits are printed. I want to detect it and fail the compilation.

Expected usage:

uint64_t time = systime_get();
MY_MACRO_3("Starting execution at systime %x%x", (uint32_t)(time >> 32), (uint32_t)time); // OK! 

The type of the ternary ?: expression is the common type of its second and third arguments (with integer promotion of smaller types). So the following version of your MY_MACRO will work in a 32-bit architecture:

static_assert(sizeof(uint32_t) == sizeof 0, ""); // sanity check, for your machine

#define MY_MACRO(arg0, arg1, arg2) \
    do {  \
        static_assert(sizeof(0 ? 0 : (arg0)) == sizeof 0, "");  \
        static_assert(sizeof(0 ? 0 : (arg1)) == sizeof 0, "");  \
        static_assert(sizeof(0 ? 0 : (arg2)) == sizeof 0, "");  \
        my_macro_impl((uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2));  \
    } while (0)

Moreover, this solution should work with all versions of C and C++ (with, if necessary, a suitable definition of static_assert).

Note this macro, like the OP's original, has function semantics in that the arguments are evaluated only once, unlike for example the notorious MAX macro.

GNU Gnulib: Compile-time Assertions, Both accept an integer constant expression argument V and verify that it is nonzero. If not, a compile-time error results. These two macros implement compile-time  !macro!macroend!macroundef!searchparse!searchreplace; Chapter 5: Compile Time Commands 5.1 Compiler Utility Commands. These commands are similar to the C preprocessor in terms of purpose and functionality. They allow file inclusion, conditional compilation, executable header packing and process execution during the build process.


The following approach may work for this need:

#define CHECK_ARG(arg)                  _Generic((arg), \
                                                 int64_t  : (arg),  \
                                                 uint64_t : (arg),  \
                                                 default  : (uint32_t)(arg))

Then, the MY_MACRO can be defined as

#define MY_MACRO(a0, a1, a2)       do \
                                   {  \
                                       uint32_t arg1 = CHECK_ARG(a0);  \
                                       uint32_t arg2 = CHECK_ARG(a1);  \
                                       uint32_t arg3 = CHECK_ARG(a2);  \
                                       my_macro_impl(arg1, arg2, arg3);\
                                   } while (0)

In such case, when passing for example uint64_t, a warning is fired:

warning: implicit conversion loses integer precision: 'uint64_t' (aka 'unsigned long long') to 'uint32_t' (aka 'unsigned int') [-Wshorten-64-to-32]

Note:

Other types like double, 128/256 bit types can be handled similarly.

Appropriate warnings should be enabled.

EDIT:

Inspired by Lundin's comment and answer, the proposed above solution can easily be modified to a portable version which will cause compilation error and not just a compiler warning.

#define CHECK_ARG(arg)          _Generic((arg),         \
                                         int64_t  : 0,  \
                                         uint64_t : 0,  \
                                         default  : 1)

So the MY_MACRO can be modified to

#define MY_MACRO(a0, a1, a2)       do \
                                   {  \
                                       _Static_assert(CHECK_ARG(a1) && \
                                                      CHECK_ARG(a2) && \
                                                      CHECK_ARG(a3),   \
                                                      "64 bit parameters are not supported!"); \
                                       my_macro_impl((uint32_t)(a1), (uint32_t)(a2), (uint32_t)(a3)); \
                                   } while (0)

This time, when passing uint64_t parameter MY_MACRO(1ULL, 0, -1), the compilation fails with error:

error: static_assert failed due to requirement '_Generic((1ULL), long long: 0, unsigned long long: 0, default: 1) && (_Generic((0), long long: 0, unsigned long long: 0, default: 1) && _Generic((-1), long long: 0, unsigned long long: 0, default: 1))' "64 bit parameters are not supported!"

C Trick: Compile-time assertion, A compile-time assert in pure standard C89 is possible, and a little bit of will cryptically refer to declaration of a negative size (GCC says “size of array foo is for the array type that hints that this error really is an assertion check. My usual solution has been to require that the macro have two parameters. In conditional compilation, particular blocks of code in a program are compiled selectively while others are ignored. For example, you may want to write debugging statements that compare the speed of different approaches to the same programming task, or you may want to localize an application for multiple languages.


Catching errors early with compile-time assertions, The trick is to use assertions that produce overt compile-time errors If your program inadvertently calls get_token with a null pointer as the first argument, the first You can use an assertion and the offsetof macro to verify that the as the size or offset of a structure member, can be done at compile time. Example #1 – VBA Array Length. First, imagine we have an array size of four elements in each row and columns i.e. four rows and four columns. So the array size would be 16. Array length is the calculated product of a number of rows and columns. Let us do this in Excel VBA.


Compile time assertions in C, A C macro to provide compile time assertions. This prompted me into seeking a more general solution of getting the compiler to check constant Note we need the 2 concats below because arguments to ## * are not  VBA Length of String – Example #3 Now, instead of output appearing in the message box, I want the result or output data to appear in the worksheet. In the worksheet, I have a data, i.e. USA state in the cell “A4”, now I want to find out a number of characters in the text string of state, Here I can use Len function with slight


Sendmail, Check to see if any minimums are required or if any warnings about maximums are Compile-time macros to redefine maximums Compile-time macro Default File per envelope (V8.12 and above) ENHSCLEN 10 sendmail/conf.h Length of map stack MAXMIMEARGS 20 sendmail/conf.h Arguments per Content-Type​:  When you use %STR or %NRSTR, the macro processor does not receive these functions and their arguments when it executes a macro. It receives only the results of these functions because these functions work when a macro compiles. By the time the macro executes, the string is already masked by a macro quoting function.


Miscellaneous Macros, Since inlining is very compiler-dependent using these macros correctly is very difficult macro lets the programmer check a condition at compile time, the condition a pointer to memory of a size that is specified by the x th function parameter. The C Preprocessor. The C preprocessor is a macro processor that is used automatically by the C compiler to transform your program before actual compilation. It is called a macro processor because it allows you to define macros, which are brief abbreviations for longer constructs.