Enforce strong type checking in C (type strictness for typedefs)
Asked Answered
E

9

48

Is there a way to enforce explicit cast for typedefs of the same type? I've to deal with utf8 and sometimes I get confused with the indices for the character count and the byte count. So it be nice to have some typedefs:

typedef unsigned int char_idx_t;
typedef unsigned int byte_idx_t;

With the addition that you need an explicit cast between them:

char_idx_t a = 0;
byte_idx_t b;

b = a; // compile warning
b = (byte_idx_t) a; // ok

I know that such a feature doesn't exist in C, but maybe you know a trick or a compiler extension (preferable gcc) that does that.


EDIT I still don't really like the Hungarian notation in general. I couldn't use it for this problem because of project coding conventions, but I used it now in another similar case, where also the types are the same and the meanings are very similar. And I have to admit: it helps. I never would go and declare every integer with a starting "i", but as in Joel's example for overlapping types, it can be life saving.

Extramarital answered 17/12, 2008 at 23:33 Comment(2)
There is another very nice article (Although i don't agree to ban goto :) ) from Joel named Making Wrong Code Look Wrong. I think it's very related to your question even if there is not direct connection.Sybilsybila
The most 'interesting' and important thing in that article, IMHO, is the bit about the history of Hungarian Notation. Veeery interesting...Craddock
A
29

For "handle" types (opaque pointers), Microsoft uses the trick of declaring structures and then typedef'ing a pointer to the structure:

#define DECLARE_HANDLE(name) struct name##__ { int unused; }; \
                             typedef struct name##__ *name

Then instead of

typedef void* FOOHANDLE;
typedef void* BARHANDLE;

They do:

DECLARE_HANDLE(FOOHANDLE);
DECLARE_HANDLE(BARHANDLE);

So now, this works:

FOOHANDLE make_foo();
BARHANDLE make_bar();
void do_bar(BARHANDLE);

FOOHANDLE foo = make_foo();  /* ok */
BARHANDLE bar = foo;         /* won't work! */
do_bar(foo);                 /* won't work! */   
Anatol answered 17/12, 2008 at 23:49 Comment(0)
T
21

You could do something like:

typedef struct {
    unsigned int c_idx;
} char_idx;

typedef struct {
    unsigned int b_idx;
} byte_idx;

Then you would see when you are using each:

char_idx a;
byte_idx b;

b.b_idx = a.c_idx;  

Now it is more clear that they are different types but would still compile.

Transition answered 17/12, 2008 at 23:45 Comment(0)
L
18

What you want is called "strong typedef" or "strict typedef".

Some programming languages [Rust, D, Haskell, Ada, ...] give some support for this at language level, C[++] does not. There was a proposal to include it into the language with the name "opaque typedef", but was not accepted.

The lack of language support is really not a problem though. Just wrap the type to be aliased into a new class having exactly 1 data member, of type T. Much of the repetition can be factored out by templates and macros. This simple technique is just as convenient as in the programming languages with direct support.

Lindsy answered 23/10, 2009 at 16:5 Comment(0)
E
9

Use a lint. See Splint:Types and strong type check.

Strong type checking often reveals programming errors. Splint can check primitive C types more strictly and flexibly than typical compilers (4.1) and provides support a Boolean type (4.2). In addition, users can define abstract types that provide information hiding (0).

Edytheee answered 17/12, 2008 at 23:51 Comment(0)
H
5

In C, the only distinction between user-defined types that is enforced by the compiler is the distinction between structs. Any typedef involving distinct structs will work. Your major design question is should different struct types use the same member names? If so, you can simulate some polymorphic code using macros and other scurvy tricks. If not, you are really committed to two different representations. E.g., do you want to be able to

#define INCREMENT(s, k) ((s).n += (k))

and use INCREMENT on both byte_idx and char_idx? Then name the fields identically.

Herries answered 18/12, 2008 at 4:21 Comment(0)
U
4

With C++11 you can use an enum class, e.g.

enum class char_idx_t : unsigned int {};
enum class byte_idx_t : unsigned int {};

The compiler will enforce an explicit cast between the two types; it is like a thin wrapper class. Unfortunately you won't have operator overloading, e.g. if you want to add two char_idx_t together you will have to cast them to unsigned int.

Ultramontane answered 24/10, 2013 at 17:36 Comment(1)
See proposal revision (3) open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdfCarbonyl
H
3

You asked about extensions. Jeff Foster's CQual is very nice, and I think it could do the job you want.

Herries answered 26/2, 2009 at 6:35 Comment(1)
Are there similar programs that are still active (the last update to the webpage of CQual was in 2004)Extravagance
O
2

If you were writing C++, you could make two identically defined classes with different names that were wrappers around an unsigned int. I don't know of a trick to do what you want in C.

Offoffbroadway answered 17/12, 2008 at 23:36 Comment(1)
C works the same way with structs. Microsoft uses this to differentiate handle types in windows.h.Anatol
P
1

Use strong typedef as defined in BOOST_STRONG_TYPEDEF

Preliminary answered 2/5, 2010 at 13:53 Comment(1)
Boost is a C++ library, and therefore irrelevant.Naca

© 2022 - 2024 — McMap. All rights reserved.