2

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.

hugomg
  • 68,213
  • 24
  • 160
  • 246
arynhard
  • 473
  • 2
  • 8
  • 26
  • Not possible with `#define`s, the compiler-proper doesn't see them, it only sees the replaced values. – Mat Oct 12 '12 at 19:28
  • named enums? can you do it with that? i feel like you can with C++, but not sure how strong they are in C – im so confused Oct 12 '12 at 19:28
  • Is there any other method that would do this? – arynhard Oct 12 '12 at 19:29
  • You can't force it to receive a specific value, even if you were in C++ and used `enum`s (since `enum` is just an `int`). You'd better check inside the function for the parameter value and report an error if it is out ot allowed range (You can use `assert`) –  Oct 12 '12 at 19:29

4 Answers4

3
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;
    }
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    The latter suggestion doesn't really improve anything -- someone could still call it with `(struct IO_Param){ 4 }`. – Chris Dodd Oct 12 '12 at 19:33
  • I can still do this: `IO_Param m; m.io_type = 40;` :P –  Oct 12 '12 at 19:34
  • @ChrisDodd: Agreed; I added the content of your comment to my answer (of my own volition) at about the same time that you added your comment. – Jonathan Leffler Oct 12 '12 at 19:36
1

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);
Wug
  • 12,956
  • 4
  • 34
  • 54
  • It would be wise to note that this is only en effort to hint users of the function, but not a way of restricting them from passing something else if they really want to. –  Oct 12 '12 at 19:29
  • You mean kind of like I already did? "You can still pass any int to it as long as you use a cast." – Wug Oct 12 '12 at 19:30
1

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 );
William Pursell
  • 204,365
  • 48
  • 270
  • 300
1

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
Community
  • 1
  • 1
Joseph Quinsey
  • 9,553
  • 10
  • 54
  • 77