arynhard
arynhard

Reputation: 473

Force function to accept specific definitions only?

I would like to force a functions parameters to accept only specific definitions. For example, consider #define OUTPUT 1, #define INPUT 0 and void restrictedFunction(int parameter); .

How would I force restrictedFunction(int parameter) to accept only OUTPUT or INPUT?

I would also like to take into consideration that another definition may have the same value, for example, #define LEFT 1 and #define RIGHT 0.

So in this case I would like restrictedFunction(int parameter) to be able to accept only OUTPUT and INPUT specifically.

Upvotes: 2

Views: 171

Answers (4)

Joseph Quinsey
Joseph Quinsey

Reputation: 9972

You can use a wrapper to validate the argument:

#define restrictedFunction(x) do {                          \
   static_assert((x) == INPUT || (x) == OUTPUT);            \
   assert(!strcmp(#x, "INPUT") || !strcmp(#x, "OUTPUT"));   \
   restrictedFunction(x);                                   \
} while(0)

Notes:

  • This assumes restrictedFunction() returns a void. If it returns a value which you actually use, you'll need something like gcc's compound statement http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html. Or--better--you can use BUILD_BUG_ON_ZERO (see What is ":-!!" in C code?), which I keep forgetting about, because it doesn't seem to work with C++.
  • The do ... while(0) is to "swallow the semi-colon"; not really relevant here.
  • static_assert() is a compile-time assert; there are many variants available. Here is a link to one, https://stackoverflow.com/a/9059896/318716, if you don't have your own handy.
  • assert() is the standard run-time assert.
  • With gcc 4.1.2, and my version of static_assert(), you can replace the run-time assert() with a compile-time assert when the two !strcmp()'s are replaced with ==; see example below. I haven't tested this with other compilers.
  • x is only used once in the macro expansion, since the first four references are only used at compile-time.

When your actually define your function, you'll have to add parentheses to disable the macro expansion, as in:

void (restrictedFunction)(int x){ ... }

Also, if your code has a special case (whose code doesn't?) where you need to call restrictedFunction() with the argument foo, you'll need to write:

  (restrictedFunction)(foo);

Here is a complete example, which puts a wrapper around the standard library function exit():

#include <stdlib.h>

#define CONCAT_TOKENS(a, b)     a ## b
#define EXPAND_THEN_CONCAT(a,b) CONCAT_TOKENS(a, b)
#define ASSERT(e)    enum{EXPAND_THEN_CONCAT(ASSERT_line_,__LINE__) = 1/!!(e)}
#define ASSERTM(e,m) enum{EXPAND_THEN_CONCAT(m##_ASSERT_line_,__LINE__)=1/!!(e)}

#define exit(x) do {                                                \
   ASSERTM((x) ==  EXIT_SUCCESS  || (x) ==  EXIT_FAILURE,  value);  \
   ASSERTM(#x  == "EXIT_SUCCESS" || #x  == "EXIT_FAILURE", symbol); \
   exit(x);                                                         \
} while(0)

int main(void) {
   exit(EXIT_SUCCESS); // good
   exit(EXIT_FAILURE); // good
   exit(0);  // bad
   exit(3);  // doubly bad
}

If I try to compile it, I get:

gcc foo.c -o foo
foo.c: In function 'main':
foo.c:17: error: enumerator value for 'symbol_ASSERT_line_17' is not an integer constant
foo.c:18: warning: division by zero
foo.c:18: error: enumerator value for 'value_ASSERT_line_18' is not an integer constant
foo.c:18: error: enumerator value for 'symbol_ASSERT_line_18' is not an integer constant

Upvotes: 1

Jonathan Leffler
Jonathan Leffler

Reputation: 754410

typedef enum { INPUT = 0, OUTPUT = 1 } IO_Type;

void restrictedFunction(IO_Type parameter) { ... }

It doesn't absolutely force the use of the values (the compiler will let someone write restrictedFunction(4)), but it is about as good as you'll get.

If you truly want to force the correct type, then:

typedef enum { INPUT = 0, OUTPUT = 1 } IO_Type;
typedef struct { IO_Type io_type } IO_Param;

void restrictedFunction(IO_Param parameter) { ... }

In C99 or later, you could call that with:

restrictedFunction((IO_Param){ INPUT });

This is a compound literal, creating a structure on the fly. It is not entirely clear that the structure type really buys you very much, but it will force the users to think a little and may improve the diagnostics from the compiler when they use it wrong (but they can probably use restrictedFunction((IO_Param){ 4 }); still).

What this means is that your restrictedFunction() code should be ready to validate the argument:

void restrictedFunction(IO_Type io_type)
{
    switch (io_type)
    {
    case INPUT:
        ...do input handling...
        break;
    case OUTPUT:
        ...do output handling...
        break;
    default:
        assert(io_type != INPUT && io_type != OUTPUT);
        ...or other error handling...
        break;
    }
}

Upvotes: 3

William Pursell
William Pursell

Reputation: 212354

You don't get quite as much protection as you might like, but you can do:

enum func_type { INPUT, OUTPUT };
void restrictedFunction( enum func_type parameter );

Upvotes: 1

Wug
Wug

Reputation: 13196

You could use an enum.

typedef enum TrafficDirection { INPUT = 0, OUTPUT = 1 } TrafficDirection;

restrictedFunction(TrafficDirection direction);

of course, this isn't perfect. You can still pass any int to it as long as you use a cast.

restrictedFunction((TrafficDirection) 4);

Upvotes: 1

Related Questions