295

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?

11 Answers 11

202

You can do forward typedef. But to do

typedef A B; 

you must first forward declare A:

class A; typedef A B; 
Sign up to request clarification or add additional context in comments.

10 Comments

+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.
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.
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.
Also this shows "implementation details" (even if not fully but still...) while the idea behind forward declaration was to hide them.
@windfinder: It does: template<class T> class A; typedef A<C> B;
|
58

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; } 

2 Comments

@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.
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?
40

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.

typedef class MyClass myclass_t; 

2 Comments

How is this different from the most voted question? stackoverflow.com/a/804956/931303
@JorgeLeitão you don't see how it's different? It doesn't show how to do that in one line.
16

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); 

4 Comments

Maintenance No No. This sort of thing will bite you in the keister sooner or later.
@MarkStorer, at least the compiler will catch any difference and generate an error. I verified this with Visual C++.
Nice, but how do you define A fields in this way since A is empty by definition ?
Repeated typedef is allowed in the latest standard of c
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.

2 Comments

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.
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.
8

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 

Comments

4

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; 

Comments

1

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 

Comments

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; }; 

Edit: Reading this back 8 years later... don't do this. You can just do

// LibraryPublicHeader.h class Implementation; class Library { ... private: std::unique_ptr<Implementation> impl; }; 

IIRC this used to only work with unique_ptr but they changed the rules a while back so it works with shared_ptr too.

Comments

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 >> { }; 

Comments

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.