tags:

views:

459

answers:

6

I am restricted to C (cannot use C++). I wish C had stricter type checking.

Is there a way to get compile errors on the commented lines? If it helps, the enum values cannot overlap.


enum hundred {
    VALUE_HUNDRED_A = 100,
    VALUE_HUNDRED_B
};

enum thousand {
    VALUE_THOUSAND_A = 1000,
    VALUE_THOUSAND_B
};

void print_hundred(enum hundred foo)
{
    switch (foo) {
        case VALUE_HUNDRED_A:     printf("hundred:a\n");     break;
        case VALUE_HUNDRED_B:     printf("hundred:b\n");     break;
        default: printf("hundred:error(%d)\n", foo); break;
    }
}

void print_thousand(enum thousand bar)
{
    switch (bar) {
        case VALUE_THOUSAND_A:     printf("thousand:a\n");     break;
        case VALUE_THOUSAND_B:     printf("thousand:b\n");     break;
        default: printf("thousand:error(%d)\n", bar); break;
    }
}

int main(void)
{
    print_hundred(VALUE_HUNDRED_A);
    print_hundred(VALUE_THOUSAND_A);  /* Want a compile error here */

    print_thousand(VALUE_THOUSAND_A);
    print_thousand(VALUE_HUNDRED_A);  /* Want a compile error here */

    return 0;
}
A: 

I would argue that the problem isn't as much that C doesn't support strict type checking, as it's that it really doesn't support true user-defined types.

My guess is that most C compilers would translate both of your enums into simple ints or shorts or whatever, and would not do anything beyond that.

So as far as I can tell, the answer would be know.

Uri
A: 

There's no way C alone can do it because the compiler doesn't know anything except the base types. The usual thing is to use the assert() macro, but that's a run-time check.

Charlie Martin
+1  A: 

You can't do it. In C++ you could overload the function and do some trickery (or use boost::enable_if), or just rely on C++'s type safety making it error out automatically. In C, that doesn't work since function overloading is not supported. And you can't check the value in the function and cause a compile time error, since all values are known only at runtime (as opposed to types).

The C Standard allows compilers to warn about what you do. So you could enable the -Wall -Werror flag and hope gcc will error out. But this is not a general purpose C way.

Johannes Schaub - litb
+8  A: 

In C, enum types are indistinguishable from integers. Very annoying.

The only way forward I can think of is a kludgy workaround using structs instead of enums. Structs are generative, so the hundreds and thousands are distinct. If the calling convention is sensible (AMD64) there will be no run-time overhead.

Here's an example using structs that gets the compile-time errors you wanted. Kludgy, but it works:

#include <stdio.h>
enum hundred_e {
    VALUE_HUNDRED_A = 100,
    VALUE_HUNDRED_B
};

enum thousand_e {
    VALUE_THOUSAND_A = 1000,
    VALUE_THOUSAND_B
};

struct hundred { enum hundred_e n; };
struct thousand { enum thousand_e n; };

const struct hundred struct_hundred_a = { VALUE_HUNDRED_A }; 
const struct hundred struct_hundred_b = { VALUE_HUNDRED_B }; 
const struct thousand struct_thousand_a = { VALUE_THOUSAND_A }; 
const struct thousand struct_thousand_b = { VALUE_THOUSAND_B }; 

void print_hundred(struct hundred foo)
{
    switch (foo.n) {
        case VALUE_HUNDRED_A:     printf("hundred:a\n");     break;
        case VALUE_HUNDRED_B:     printf("hundred:b\n");     break;
        default: printf("hundred:error(%d)\n", foo.n); break;
    }
}

void print_thousand(struct thousand bar)
{
    switch (bar.n) {
        case VALUE_THOUSAND_A:     printf("thousand:a\n");     break;
        case VALUE_THOUSAND_B:     printf("thousand:b\n");     break;
        default: printf("thousand:error(%d)\n", bar.n); break;
    }
}

int main(void)
{

    print_hundred(struct_hundred_a);
    print_hundred(struct_thousand_a);  /* Want a compile error here */

    print_thousand(struct_thousand_a);
    print_thousand(struct_hundred_a);  /* Want a compile error here */

    return 0;
}
Norman Ramsey
A: 

I think strictly the answer is, "it depends on the compiler". I'm fairly sure that the code is legal C, so by default a C compiler wouldn't/shouldn't complain, but there are probably different options in different compilers that can pick it up.

If this type of error checking is important to you then I suggest investigating C linters/style checkers/static analysis tools which will catch this and other common (and not so common) errors (if you set them up correctly!). It is a bit of work to add these tools into your build process, but if for your project you think catching these kind of things at compile is valuable then the cost is going to be worth it.

Two I would recommend are:

FlexeLint, which is a relatively inexpensive commercial product that I have used to great effect.

An open source alternative would be Splint, but unfortunately it appears to be mostly unmaintained at the moment.

There are more expensive commercial tools such as Klocwork and Coverity.

Adding these kind of tools to your software does take some effort. They are usually extremely flexible and customisable, and you need to make some educated decisions about what behaviours you want to allow, and which you want to disallow in your code base.

benno
A: 

You could do it using #defines for your functions and __builtin_constant(x), which returns 1 if x resolves to a constant and 0 if it does not. Note this is a gcc-only intrinsic; I have no idea if there are equivalents on other compilers.

Dark Shikari