5

The following code compiles fine.

header.h:

typedef struct Placeholder_Type* Placeholder;

impl.cpp:

#include "header.h"

void doSomething(Placeholder t) {
  (void) t;
}
    
int main() {
  int *a = new int();
  doSomething((Placeholder)a);
}

compilation command:

clang++ impl.cpp

The type Placeholder_Type does not exist anywhere and it doesn't exist as a symbol in the output binary.

Why is it legal to create a typedef for a type that does not exist?

Why can I create a function using a type that doesn't exist?

Is this equivalent to just using void* but named "Placeholder"?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • The type does actually exist, but it's not completely defined. It's a fine distinction and allows use of so-called "forward declarations". Anyhow, the reason why this works is the C++ standard. – Ulrich Eckhardt Feb 03 '22 at 07:53
  • 1
    ot: the `#include` directive gets replaced by the preprocessor with the contents of the file. You can place all code in the cpp without changing anything, but a single file is easier to reproduce for others – 463035818_is_not_an_ai Feb 03 '22 at 07:54
  • Adding to the noodle bake. `typedef struct Placeholder_Type PlaceHolder;` and changing all of the usages in `impl.cpp` to `Placeholder *` will also compile (and in fact, that is why your code "works" (term used loosely) at all; you just shrouded a pointer-type alias whilst doing so). – WhozCraig Feb 03 '22 at 08:02

3 Answers3

4

struct Placeholder_Type declares the struct Placeholder_Type (but doesn't define it), no matter where it appears. Even if it's inside a typedef. So you don't create a typedef to a struct that doesn't exist, but to one you just declared (and thus created, if the compiler didn't already know about it).

As for why, it's because this way you can keep the definition of a struct away from the public interface. For example, this is a typical way to implement an opaque object in C:

// mytype.h
typedef struct MytypeImpl* Mytype;

Mytype create_mytype();
void destroy_mytype(Mytype o);

void do_something_with_mytype(Mytype o, int i);

// mytype.c
struct MytypeImpl {
  int something;
  int otherthing;
};
Mytype create_mytype() {
  Mytype o = malloc(sizeof(*o));
  o->something = 0;
  o->otherthing = 0;
  return o;
}
void destroy_mytype(Mytype o) {
  free(o);
}
// etc.

Individual styles may differ in details, of course. But the key point is that the definition of the struct isn't visible outside mytype.c, so nobody can just access the data members.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
1

Why not? As far as I know "exist" is not an official term. A type can be declared but not defined and it can be declared and defined. In the first case the type is said to be incomplete. There is not much you can do with an incomplete type. For example you cannot create objects of the type. You can however use pointers to an incomplete type. All the compiler needs to know is a declaration of the type. Further note that the typedef does contain a declaration of Placeholder_Type:

typedef struct Placeholder_Type* Placeholder; 
        ^---------------------^ declares the class named Placeholder_Type

As in your code you never create an object or use members of Placeholder_Type it does compile. I am not 100% sure, but I think there is also no UB. Casting the int doesn't look that nice, but as you never actually dereference the pointer there is nothing wrong.

For more inforamation I refer you to this very related questions about forward declarations: What are forward declarations in C++? When can I use a forward declaration?

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

Not all declarations are at the same time definitions.

For example you can declare a function that is not yet defined:

void func( void );

and then refer to the name func.

Or you can declare a class as for example:

class A
{
    friend class B;
    //...
};

Declarations introduce names in given scope.

Only when a name is used in an evaluation context it must be defined and has a complete type.

In this declaration

typedef struct Placeholder_Type* Placeholder;

there are introduced two names: Placeholder_Type and Placeholder. Placeholder is an alias for the pointer type struct Placeholder_Type*. Objects of pointer type are always complete objects. You can calculate its size using the expression:

sizeof( Placeholder )

It is the same as to have a pointer to the type void.

void *p;

The type void is always an incomplete type by the pointer type itself is a complete type. If you will not try to dereference the pointer or to apply the pointer arithmetic the referenced type is not required to be complete.

halfer
  • 19,824
  • 17
  • 99
  • 186
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335