Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to create a forward declaration of a typedef struct

I have 1 .h file test.h containing a class. In this class there's a private method returning a pointer on a type I don't want to make "public", but I want to be able to include this test.h file in other source files.

In general, it's easy to use a forward declaration in the .h file:

class Foo;

But the problem is that this type comes from a C file that I cannot change (because it's someone else code that I'm not maintaining) and it's a typedef.

So basically my test.cpp is:

// this type comes from a C file, cannot be changed to struct or class
typedef struct 
{
   int x;
} Foo;

#include "test.h"

static Foo foo;

Foo *Test::private_function()
{
  foo.x = 12;
  return &foo;
}

int Test::compute()
{
   auto *local_foo = private_function();
   return local_foo->x;
}

and my test.h file is:

#pragma once

struct Foo;

class Test
{
public:
  Test() {}
  int compute();
private:
  Foo *private_function();
};

trying to compile that fails:

>g++ -std=c++11 -c test.cpp
In file included from test.cpp:10:0:
test.h:3:8: error: using typedef-name 'Foo' after 'struct'
test.cpp:7:3: note: 'Foo' has a previous declaration here

Currently my workaround is to return void * and perform static_cast back and forth, but I don't find that optimal. Is there a nicer solution?

(I have checked Forward declaration of a typedef in C++ but I tested the solutions and they don't seem to work in my case, maybe what I want to do is simpler/different - I have just a .h and .cpp - or just not possible)

like image 395
Jean-François Fabre Avatar asked Sep 18 '18 13:09

Jean-François Fabre


2 Answers

Return this:

//#include "SecretFoo.h"
struct SecretFoo {
  uintptr_t handle;
};

//#include "SecretFooImpl.h"
#include "SecretFoo.h"
#include "Foo.h" // definition of typedef struct {int x;} Foo;
Foo* Crack( SecretFoo foo ) {
  return reinterpret_cast<Foo*>(foo.handle);
}
SecretFoo Encase( Foo* foo ) {
  return {reinterpret_cast<uintptr_t>(foo)};
}

now we get:

#include "SecretFooImpl.h"
static Foo foo;

SecretFoo Test::private_function()
{
  foo.x = 12;
  return Encase(&foo);
}

int Test::compute()
{
   auto *local_foo = Crack(private_function());
   return local_foo->x;
}

and in your header:

#pragma once
#include "SecretFoo.h"

class Test
{
public:
  Test() {}
  int compute();
private:
  SecretFoo private_function();
};

this boils down to the same binary code, but the SecretFoo and paired Crack/Encase functions provide a safer kind of casting than just a void*.


This technique is sometimes used in the C world. SecretFoo is a kind of handle; an opaque pointer-like structure. The data in it (the uintptr_t handle) is in this case just a cast pointer; but it could be a pointer into a table of pointers or whatever else. The Crack and Encase methods are the only ways permitted to access/create the SecretFoo.

like image 177
Yakk - Adam Nevraumont Avatar answered Oct 18 '22 03:10

Yakk - Adam Nevraumont


Unfortunately typedefs cannot be forward-declared.

A common workaround is to have a C++ class that inherits from the C struct, referenced by its typedef, and you can forward-declare that. This will require some code changes, but they should be minimal.

(Posting on behalf of https://stackoverflow.com/users/3943312/sam-varshavchik who commented)

like image 43
Jean-François Fabre Avatar answered Oct 18 '22 03:10

Jean-François Fabre