279

Why won't the compiler let me forward declare a typedef?

Assuming it's impossible, what's the best practice for keeping my inclusion tree small?

user96825
  • 2,791
  • 2
  • 16
  • 4

11 Answers11

193

You can do forward typedef. But to do

typedef A B;

you must first forward declare A:

class A;

typedef A B;
Simon
  • 31,675
  • 9
  • 80
  • 92
Hong Jiang
  • 2,260
  • 1
  • 13
  • 13
  • 12
    +1 in the end because while you technically can't "forward-typedef" (i.e. you can't write "typedef A;"), you can almost certainly accomplish what the OP wants to accomplish using your trick above. – j_random_hacker Apr 30 '09 at 07:30
  • 10
    But be aware, if the typedef changes you may change all those forward declarations too, which you might miss if the old and the new typedef using types with the same interface. – math Jan 27 '11 at 16:34
  • 65
    In general this is not a useful solution. For example if the `typedef` names a complex multilevel template type using a forward declaration this way is rather complex and difficult. Not to mention that it might require diving into implementation details hidden in default template arguments. And the end solution is a lengthy and unreadable code (especially when types come from various namespaces) very prone to change in original type. – Adam Badura Nov 29 '12 at 12:08
  • 6
    Also this shows "implementation details" (even if not fully but still...) while the idea behind forward declaration was to hide them. – Adam Badura Nov 29 '12 at 12:09
  • @AdamBadura: Good point. I agree, it is not a good practice to reflect the `typedef`. **The solution is a hack and should be applied carefully.** In some cases it is **very useful**, though. Consider an API `class A` hiding its usage of another API `class _B` by setting a member `B* b_;` private, where `typedef class _B B;`. Not to use a verbose Pimpl, you need to forward-delcare `B`. – Janusz Lenar Jan 07 '13 at 10:13
  • 3
    @windfinder: It does: template class A; typedef A B; – milianw Mar 27 '13 at 14:16
  • I struggled with this for a prototype in my header with pcre*. I looked at pcre.h and saw pcre is typedef of struct real_pcre, considered using real_pcre* in the signature and forward declare. But I had the same concerns as Adam: It felt like "bad practice" to rely on the hidden details not changing. After 5 minutes, **the light dawned on Marblehead**. I'm including this header with the implementation of that function, so what difference does it make? I can include in both files and only get 1 copy. Not being anecdotal--hopefully a little slap for anyone else struggling with this. – Luv2code Jul 09 '15 at 03:00
  • Late comment, I know – but not all typedefs can be covered that way: `typedef struct { } S;` – which is not uncommon in C headers... – Aconcagua Feb 13 '20 at 17:16
56

For those of you like me, who are looking to forward declare a C-style struct that was defined using typedef, in some c++ code, I have found a solution that goes as follows...

// a.h
 typedef struct _bah {
    int a;
    int b;
 } bah;

// b.h
 struct _bah;
 typedef _bah bah;

 class foo {
   foo(bah * b);
   foo(bah b);
   bah * mBah;
 };

// b.cpp
 #include "b.h"
 #include "a.h"

 foo::foo(bah * b) {
   mBah = b;
 }

 foo::foo(bah b) {
   mBah = &b;
 }
LittleJohn
  • 561
  • 4
  • 2
  • 6
    @LittleJohn The problem with this solution is that the dummy-name _bah is not considered as a part of the public API. See forward delcare FILE. – user877329 Aug 09 '13 at 17:42
  • I've got a similar situation (with shellapi.h), except I'm getting "error C2371: 'NOTIFYICONDATA': redefinition; different basic types" with `struct _NOTIFYICONDATA; typedef _NOTIFYICONDATA NOTIFYICONDATA;`. Not sure why but it's not treating my `struct` as forward declared.. Any ideas why? – atlex2 Aug 11 '22 at 18:42
39

To "fwd declare a typedef" you need to fwd declare a class or a struct and then you can typedef declared type. Multiple identical typedefs are acceptable by compiler.

long form:

class MyClass;
typedef MyClass myclass_t;

short form:

typedef class MyClass myclass_t;
Pavel P
  • 15,789
  • 11
  • 79
  • 128
18

In C++ (but not plain C), it's perfectly legal to typedef a type twice, so long as both definitions are completely identical:

// foo.h
struct A{};
typedef A *PA;

// bar.h
struct A;  // forward declare A
typedef A *PA;
void func(PA x);

// baz.cc
#include "bar.h"
#include "foo.h"
// We've now included the definition for PA twice, but it's ok since they're the same
...
A x;
func(&x);
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
9

Using forward declarations instead of a full #includes is possible only when you are not intending on using the type itself (in this file's scope) but a pointer or reference to it.

To use the type itself, the compiler must know its size - hence its full declaration must be seen - hence a full #include is needed.

However, the size of a pointer or reference is known to the compiler, regardless of the size of the pointee, so a forward declaration is sufficient - it declares a type identifier name.

Interestingly, when using pointer or reference to class or struct types, the compiler can handle incomplete types saving you the need to forward declare the pointee types as well:

// header.h

// Look Ma! No forward declarations!
typedef class A* APtr; // class A is an incomplete type - no fwd. decl. anywhere
typedef class A& ARef;

typedef struct B* BPtr; // struct B is an incomplete type - no fwd. decl. anywhere
typedef struct B& BRef;

// Using the name without the class/struct specifier requires fwd. decl. the type itself.    
class C;         // fwd. decl. type
typedef C* CPtr; // no class/struct specifier 
typedef C& CRef; // no class/struct specifier 

struct D;        // fwd. decl. type
typedef D* DPtr; // no class/struct specifier 
typedef D& DRef; // no class/struct specifier 
Adi Shavit
  • 16,743
  • 5
  • 67
  • 137
9

Because to declare a type, its size needs to be known. You can forward declare a pointer to the type, or typedef a pointer to the type.

If you really want to, you can use the pimpl idiom to keep the includes down. But if you want to use a type, rather than a pointer, the compiler has to know its size.

Edit: j_random_hacker adds an important qualification to this answer, basically that the size needs to be know to use the type, but a forward declaration can be made if we only need to know the type exists, in order to create pointers or references to the type. Since the OP didn't show code, but complained it wouldn't compile, I assumed (probably correctly) that the OP was trying to use the type, not just refer to it.

tpdi
  • 34,554
  • 11
  • 80
  • 120
  • 38
    Well, forward declarations of class types declare these types without knowledge of their size. Also, in addition to being able to define pointers and references to such incomplete types, functions can be declared (but not defined) which take parameters and/or return a value of such types. – j_random_hacker Apr 30 '09 at 07:47
  • 3
    Sorry I don't think that is a good assumption. This answer is beside the point. This is very much the case of typedef a forward declaration. – Cookie Jul 22 '11 at 16:27
2

I had the same issue, didn't want to mess with multiple typedefs in different files, so I resolved it with inheritance:

was:

class BurstBoss {

public:

    typedef std::pair<Ogre::ParticleSystem*, bool> ParticleSystem; // removed this with...

did:

class ParticleSystem : public std::pair<Ogre::ParticleSystem*, bool>
{

public:

    ParticleSystem(Ogre::ParticleSystem* system, bool enabled) : std::pair<Ogre::ParticleSystem*, bool>(system, enabled) {
    };
};

Worked like a charm. Of course, I had to change any references from

BurstBoss::ParticleSystem

to simply

ParticleSystem
Bill Kotsias
  • 3,258
  • 6
  • 33
  • 60
2

I replaced the typedef (using to be specific) with inheritance and constructor inheritance (?).

Original

using CallStack = std::array<StackFrame, MAX_CALLSTACK_DEPTH>;

Replaced

struct CallStack // Not a typedef to allow forward declaration.
  : public std::array<StackFrame, MAX_CALLSTACK_DEPTH>
{
  typedef std::array<StackFrame, MAX_CALLSTACK_DEPTH> Base;
  using Base::Base;
};

This way I was able to forward declare CallStack with:

class CallStack;
Notinlist
  • 16,144
  • 10
  • 57
  • 99
1

As Bill Kotsias noted, the only reasonable way to keep the typedef details of your point private, and forward declare them is with inheritance. You can do it a bit nicer with C++11 though. Consider this:

// LibraryPublicHeader.h

class Implementation;

class Library
{
...
private:
    Implementation* impl;
};
// LibraryPrivateImplementation.cpp

// This annoyingly does not work:
//
//     typedef std::shared_ptr<Foo> Implementation;

// However this does, and is almost as good.
class Implementation : public std::shared_ptr<Foo>
{
public:
    // C++11 allows us to easily copy all the constructors.
    using shared_ptr::shared_ptr;
};
Timmmm
  • 88,195
  • 71
  • 364
  • 509
0

Like @BillKotsias, I used inheritance, and it worked for me.

I changed this mess (which required all the boost headers in my declaration *.h)

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/moment.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>

typedef boost::accumulators::accumulator_set<float,
 boost::accumulators::features<
  boost::accumulators::tag::median,
  boost::accumulators::tag::mean,
  boost::accumulators::tag::min,
  boost::accumulators::tag::max
 >> VanillaAccumulator_t ;
std::unique_ptr<VanillaAccumulator_t> acc;

into this declaration (*.h)

class VanillaAccumulator;
std::unique_ptr<VanillaAccumulator> acc;

and the implementation (*.cpp) was

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/moment.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>

class VanillaAccumulator : public
  boost::accumulators::accumulator_set<float,
    boost::accumulators::features<
      boost::accumulators::tag::median,
      boost::accumulators::tag::mean,
      boost::accumulators::tag::min,
      boost::accumulators::tag::max
>>
{
};
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
0

Another solution is to put the forward declarations and typedefs into a separate header and include that:

// ForwardDeclarations.h
#pragma once
namespace Foo
{
    struct Bar;
    typedef Bar Baz;
}

// SomeFile.h
#include "ForwardDeclarations.h"
Foo::Baz baz;

Of course this doesn't actually reduce the number of files to include and the compiler still has to read this file from the disk, but at least the contents are simpler than the full definition. You could add more forward declarations to the same file and include it in relevant places.

Hunaja
  • 89
  • 1
  • 5