19

Using only the features of C89, given

typedef [unspecified token sequence] T1;
typedef [another unspecified token sequence] T2;

exhibit a language construct which will compile without error if and only if T1 and T2 are the same type (not just compatible). The limitation to C89 is because this is going into an autoconf probe.

EDIT: I need a solution which works even if T1 or T2 or both are incomplete types. Sorry for not mentioning this before.

SON OF EDIT: All three current answers only detect compatible types. This turns out to be much closer to "the same type" than I remembered, close enough for my current purposes, but out of curiosity I am still looking for an answer that detects the same type. Here are some pairs of types that are compatible but not the same:

typedef void (*T1)(void);
typedef void (*T2)();

typedef float T1[];
typedef float T2[12];

typedef enum { ONE, TWO, THREE } T1;
typedef /* implementation-defined integer type */ T2;
zwol
  • 135,547
  • 38
  • 252
  • 361
  • I suppose a compiler warning is not good enough? – Nikos C. Feb 20 '13 at 18:50
  • @NikosC. If compiled with `-Werror`, a warning will become an error, so that is probably fine. – Kevin Feb 20 '13 at 19:00
  • I'd prefer a hard error for operational reasons (not having to muck with CFLAGS in the autoconf probe) but a warning will do. – zwol Feb 20 '13 at 19:03
  • see: http://stackoverflow.com/questions/9229601/what-is-in-c-code – technosaurus Feb 21 '13 at 02:22
  • @technosaurus There are several such tricks for failing the build if an *expression* (technically, an integer constant expression) evaluates to some fixed value, but as far as I know, none of them can be applied to test *type* equality in C89. – zwol Feb 21 '13 at 15:06
  • It just occured to me that compatible types can be used interchangeably actually. I had the impression that "compatible" means something like `int` vs `unsigned`. But no. Those are actually not compatible. For all intents and purposes I can think of, compatible types *can* be treated as equal. So why is type compatibility not good enough in your case? – Nikos C. Feb 21 '13 at 16:54
  • @NikosC. If you're writing a library with headers that can be used from both C and C++, you have to worry about name-mangling mismatches in C++ library-users -- even if the library itself is C-only and properly `extern "C"`-tagged. Name mangling uses a stricter definition of 'the same type' than C89's 'compatible'; for instance, in your `enum` vs `int` example, T1 and T2 would mangle differently. I don't want to have to have a C++ compiler around for the library build just to detect this kind of type mismatch (it would be the only thing it was used for). – zwol Feb 21 '13 at 18:31
  • @NikosC. That said, I may not *need* to probe any types for which the difference matters. It looks like `enum` vs. underlying integer type is the only scenario liable to come up in practice, and for the specific thing I'm doing, it shouldn't happen. – zwol Feb 21 '13 at 18:32

2 Answers2

7

I think you should be able to utilize the strict type checking of extern declarations:

typedef int T1;
typedef char T2;

extern T1 t1;    
T2 t1;

The above will not compile. Changing T2 to an int will allow the source to build correctly.

This will also not compile:

typedef int T1;
typedef unsigned int T2;

extern T1 t1;    
T2 t1;

Even though both types are int. Which I think is what you want.

However, this will not trigger an error:

typedef emum {Foo} T1;
typedef unsigned T2;

So it's not 100% waterproof. However, one has to keep in mind that there's nothing one can do with an enum that can't also be done with an unsigned. They have the same layout and can be used interchangeably. Effectively, they are the same type.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • Nice! Yes, this is the behavior I want. I'm going to check exactly what the standard says about this construct before I accept your answer, but I'm pretty sure you're right. – zwol Feb 20 '13 at 19:04
  • On checking C99, this doesn't work if `T1` or `T2` or both are incomplete types. This is fixable by making both declarations of `t1` be pointers to `T1` and `T2` respectively. Also, technically it only requires T1 and T2 to be *compatible* types. That turns out to be much closer to "the same type" than I remembered, and will probably do for my purposes, but I'm still curious whether strict type equality can be detected. – zwol Feb 21 '13 at 00:54
  • @Zack I'm not sure what you mean. If the types aren't the same, I can't get this to compile. Can you give an example where it compiles while the types aren't the same? – Nikos C. Feb 21 '13 at 01:32
  • @Zack Never mind, I found a counter example by using an emum. – Nikos C. Feb 21 '13 at 01:55
2
T1 t1;
T2 *t2 = &t1;

This is a constraint violation if T1 and T2 are not the same.

Or,

T1 f();
T2 f() {}
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • These also only detect *compatible* types, not *the same* type. – zwol Feb 21 '13 at 00:59
  • Hmm. Shouldn't the latter require the *same* type, not just compatible? – R.. GitHub STOP HELPING ICE Feb 21 '13 at 01:21
  • The function prototype return type approach seems to have the same results as the `extern` answer, including not catching `enum` vs `unsigned` as being different. – Nikos C. Feb 21 '13 at 01:57
  • 1
    I can't find any constraint (in C99; I don't have an electronic copy of C89) for function definitions which is more stringent than 6.7p4 "All declarations in the same scope that refer to the same object or function shall specify compatible types." And 6.7.5.3p15 says "For two function types to be compatible, both shall specify _compatible_ return types. Moreover, [...]" (emphasis mine). – zwol Feb 21 '13 at 15:12