Hanans
Hanans

Reputation: 103

Is it possible to force a function argument to be constant in C?

Is there a way in C to force a function caller to send a constant value (expect compilation error if not) to the function?

for example, consider this simple function:

int foo(int x, int y)
{
    assert( x < 10);
    return x+y;
}

if I can somehow tell the compiler that x should be sent with a constant value in all cases, the assert can be evaluated at compile time (even if the assert is actually called only in run time). Is there a way to do so?

Just to explain the motivation - in low resources systems having things evaluated at compile time can lower SW footprint and execution time significantly.

Thanks.

Upvotes: 4

Views: 444

Answers (1)

First and foremost, here it is, a fully standard C11 solution:

#include <stdint.h>
#include <assert.h>

#define is_integral_constant_p(c) (_Generic(0 ? (void*)(uintptr_t)((c) - (c)) : (int*)(0), \
                                  int*: 1,                                            \
                                  default: 0)) 


#define foo(x, y) (is_integral_constant_p(x) ? real_foo : error_foo_x_is_not_a_constant_expression)(x, y)

extern int error_foo_x_is_not_a_constant_expression(int, int);
int real_foo(int x, int y)
{
    assert(x < 10);
    return x+y;
}

int main(void)
{
    static_assert(is_integral_constant_p(5), "5 is not a constant expression?");
    foo(5, 1);

    // int x = 0;
    // foo(x, 1);  // This will not link
}

The magic is in how the conditional expression works. If one of the operands is a null pointer constant, the type of the result is the type of the other pointer argument. Otherwise, if one argument is a void* the result is void*. Since (void*)(0) is a null pointer constant, the second operand is a null pointer constant only when (c) - (c) is an integral constant expression equal to 0. Which happens iff c is an integral constant expression.

The _Generic just selects a constant based on the type of the result, which we expect to be either int* in the true case, or void* otherwise. What's more the macro itself evaluates to an integral constant expression (and so could even be used in an assert).

The above is an adaptation of a technique used in the linux kernel (as discussed here), only without and GNU specific extension to C. Only C11 features are used.

Now, we implement foo as a macro that does our check on x, and either forwards to the "real_foo" that does the calculation, or to a function that is declared but not defined. So the program fails to link if x is not a constant.

See it live here.


As for your particular case, you can remove the assertion entirely and move the check into the foo macro. It would look something like this:

#define foo(x, y) (is_integral_constat_p(x) && (x < 10) ? real_foo : \
                   error_foo_x_is_not_a_constant_expression_or_more_than_10)(x, y)

The macro, combined with short circuit evaluation gets us the behavior you want. As you can see here.

At this point I feel I should caution you. This code may not be so straight-forward to protect in code review. So think carefully about whether or not any performance gain you may get is worth the explanations.

Upvotes: 5

Related Questions