Imponi il controllo dei tipi intensi in C (tipo strictness per typedefs)

C’è un modo per forzare il cast esplicito per typedef dello stesso tipo? Ho a che fare con utf8 e a volte mi confondo con gli indici per il conteggio dei caratteri e il conteggio dei byte. Quindi è bello avere alcuni typedef:

typedef unsigned int char_idx_t; typedef unsigned int byte_idx_t; 

Con l’aggiunta che è necessario un cast esplicito tra di loro:

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

So che una caratteristica del genere non esiste in C, ma forse si conosce un trucco o un’estensione del compilatore (preferibile gcc) che lo fa.


EDIT Ancora non mi piace molto la notazione ungherese in generale. Non potrei usarlo per questo problema a causa delle convenzioni di codifica del progetto, ma l’ho usato ora in un altro caso simile, dove anche i tipi sono gli stessi ei significati sono molto simili. E devo ammettere: aiuta. Non andrei mai a dichiarare ogni intero con una “i” iniziale, ma come nell’esempio di Joel per i tipi sovrapposti, può essere salvavita.

Per i tipi “handle” (puntatori opachi), Microsoft utilizza il trucco per dichiarare le strutture e quindi digitare un puntatore alla struttura:

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

Poi invece di

 typedef void* FOOHANDLE; typedef void* BARHANDLE; 

Loro fanno:

 DECLARE_HANDLE(FOOHANDLE); DECLARE_HANDLE(BARHANDLE); 

Quindi ora, questo funziona:

 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! */ 

Potresti fare qualcosa come:

 typedef struct { unsigned int c_idx; } char_idx; typedef struct { unsigned int b_idx; } byte_idx; 

Allora vedresti quando stai usando ciascuno:

 char_idx a; byte_idx b; b.b_idx = a.c_idx; 

Ora è più chiaro che sono di tipi diversi ma che sarebbero comunque compilati.

Quello che vuoi è chiamato “strong typedef” o “strict typedef”.

Alcuni linguaggi di programmazione [Rust, D, Haskell, Ada, …] forniscono un certo supporto a livello linguistico, C [++] no. C’era una proposta per includerlo nella lingua con il nome “opaque typedef”, ma non è stato accettato.

La mancanza di supporto linguistico non è comunque un problema. Basta avvolgere il tipo da aliasizzare in una nuova class avente esattamente 1 membro dati, di tipo T. Gran parte della ripetizione può essere scomposta da modelli e macro. Questa semplice tecnica è conveniente tanto quanto nei linguaggi di programmazione con supporto diretto.

Usa una lanugine. Vedi Stecca: tipi e controlli di tipo forte .

Il controllo di tipo intenso rivela spesso errori di programmazione. Splint può controllare i tipi C primitivi in ​​modo più rigido e flessibile rispetto ai compilatori tipici (4.1) e fornisce un supporto per un tipo booleano (4.2). Inoltre, gli utenti possono definire tipi astratti che forniscono informazioni nascoste (0).

In C, l’unica distinzione tra i tipi definiti dall’utente che vengono applicati dal compilatore è la distinzione tra le strutture . Funzionerà qualsiasi typedef che coinvolge strutture distinte. La tua principale domanda di progettazione è se i diversi tipi di struttura utilizzano gli stessi nomi dei membri? Se è così, puoi simulare un codice polimorfico usando macro e altri trucchi dello scorbuto. In caso contrario, sei veramente impegnato a due diverse rappresentazioni. Ad esempio, vuoi essere in grado di

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

e utilizzare INCREMENT su byte_idx e char_idx ? Quindi denominare i campi in modo identico.

Hai chiesto delle estensioni. Il CQual di Jeff Foster è molto carino e penso che potrebbe fare il lavoro che desideri.

Se stavate scrivendo C ++, potreste fare due classi identicamente definite con nomi diversi che erano wrapper attorno a un int unsigned. Non conosco un trucco per fare ciò che vuoi in C.

Utilizza strong typedef come definito in BOOST_STRONG_TYPEDEF

Con C ++ 11 puoi usare una class enum, ad es

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

Il compilatore farà rispettare un cast esplicito tra i due tipi; è come una class di wrapper sottile. Sfortunatamente non avrai sovraccarico dell’operatore, ad esempio se vuoi aggiungere due char_idx_t insieme dovrai lanciarli su int unsigned.