Background
I am working with a collection of opaque types of my own design ("the collection").
At higher levels of my program, I want to pass around handles to each instance of each object having a type in the collection. The lower levels of my program (that know about the details of the type) deal with the underlying structure associated with each type, and apply appropriate operations.
One reason to use this approach—in the case of struct
s—is (N2176 6.2.5-28):
All pointers to structure types shall have the same representation and alignment requirements as each other.
I want each type to be distinct (there is no inheritance or polymorphism among members of the collection) so that I can take advantage of compile-time detection of type errors. Also, I don't think I understand the difference between "distinct" and not "compatible:" "Two types have compatible type if their types are the same" (ibid. 6.2.7-1).
Headers are similar to
// FILE: value.h
#include <stddef.h>
typedef struct s_myValue myValue;
typedef myValue * myHandle;
typedef const myHandle constMyHandle; // Oops. See comments on typedef.
int value_init(myHandle, size_t);
int value_f1(myHandle, ...);
int value_f2(myHandle, ...);
or
// FILE: value-1.h
#include <stddef.h>
typedef struct s_myValue myValue;
int value_init(myValue *, size_t);
int value_f1(myValue *, ...);
int value_f2(myValue *, ...);
int value_f3(const myValue *, ...);
(Credit goes to another SO user for suggesting these kind of typedef
s—can't seem to find a better reference just now.)
In one case, I have decided that at the lower level, I am going to demote each myHandle
to a void *
for internal processing. Therefore (I think), the only reason to have myValue
, myHandle
, and constMyHandle
is to provide for the interface, and it does not matter how I define struct s_myValue
.
Except that all members of the collection must be distinct. How to guarantee this?
Prompted by ibid. (6.7.2.3-5):
Each declaration of a structure, union, or enumerated type which does not include a tag declares a distinct type.
and the specification of sytax for declarations (ibid., A.2.2), I have come up with the following minimal (I think) declaration:
Declaration No. 1 (wrong)
// FILE: value.c
#include "value.h"
// ...
struct s_myValue
{
_Static_assert ( 1 , "" ) ;
} ;
// ...
But this seems kludgey. Also, not valid, as pointed out by @kamilcuk (N2176 6.2.5-20):
A structure type describes a sequentially allocated nonempty set of member objects... each of which has an optionally specified name and possibly distinct type.
Declaration No. 2
Maybe:
// FILE: value.c
#include "value.h"
// ...
struct s_myValue
{
// tag randomly generated (UUID4, reorganized)
s_myValue * a6e64fd2eb4689eab294b9524e0efa1 ;
} ;
// ...
But, yuck.
The Question
Is there a more expressive, elegant way to declare a struct
that is distinct from all other types? Is there a way to declare that is more consistent with the design of the C programming language? (Yes, I realize that I just used the words "design" and "C" in the same sentence.)
I don't think struct s_myValue { } ;
is a candidate, because that declaration seems not to be standard.
Appendix (MRE)
The point is to see how the types impact compilation.
//==> bar.h <==
#ifndef H_BAR
#define H_BAR
typedef struct s_bar bar ;
void bar_init ( bar * ) ;
#endif
//==> bar.c <==
#include "bar.h"
struct s_bar { int i ; } ;
void bar_init ( bar * b ) { ; }
//==> baz.h <==
#ifndef H_BAZ
#define H_BAZ
typedef struct s_baz baz ;
void baz_init ( baz * ) ;
#endif
//==> baz.c <==
#include "baz.h"
struct s_baz { int i ; } ;
void baz_init ( baz * b ) { ; }
//==> foo.h <== (EMPTY FILE)
//==> foo.c <==
#include "bar.h"
#include "baz.h"
#include <stdlib.h>
int main ( void )
{
bar * pbar = NULL ; // keeping it simple for this example...
baz * pbaz = NULL ; // ... OK because init-s don't do anything.
bar_init ( pbar ) ;
baz_init ( pbar ) ; // passing wrong pointer type intentionally
return 0 ;
}
//==> Makefile <==
objects=foo.o bar.o baz.o
CC=gcc -std=c17
CCC=$(CC) -c
foo: $(objects)
$(CC) -o foo $(objects)
foo.o: foo.c foo.h bar.h baz.h
$(CCC) foo.c
foo.h: ;
foo.c: ;
bar.o: bar.c bar.h
$(CCC) bar.c
bar.h: ;
bar.c: ;
baz.o: baz.c baz.h
$(CCC) baz.c
baz.h: ;
baz.c: ;
GCC produces a warning (not an error):
foo.c: In function ‘main’:
foo.c:9:16: warning: passing argument 1 of ‘baz_init’ from incompatible
pointer type [-Wincompatible-pointer-types]
9 | baz_init ( pbar ) ; // passing wrong pointer type intentionally
| ^~~~
| |
| bar * {aka struct s_bar *}
In file included from foo.c:2:
baz.h:4:17: note: expected ‘baz *’ {aka ‘struct s_baz *’} but argument is of type ‘bar *’ {aka ‘struct s_bar *’}
4 | void baz_init ( baz * ) ;
| ^~~~~
This is not the type checking I am looking for—maybe I should switch to C++—(ibid. 6.3.2.3-7):
A pointer to an object type may be converted to a pointer to a different object type.