When can I use forward declaration?

Posted by urb on Tue, 07 Jan 2020 12:16:00 +0100

I am looking for a definition of when to allow forward declaration of a class in the header file of another class:

Can I do this for a base class, a class held as a member, a class passed to a member function by reference, etc?

#1 building

Lakos Differentiated usage

  1. Only in the name (forward declaration is sufficient for this declaration) and
  2. In size (you need to define a class for it).

I've never seen it pronounce more succinctly:)

#2 building

I see it as a separate answer, not just a comment, because I disagree with Luc Touraille's answer, not based on legitimacy, but on the dangers of powerful software and misinterpretation.

Specifically, I have questions about the implied contracts that your interface users want to understand.

If you want to return or accept reference types, you just say that they can be passed through pointers or references, which can only be known through forward declarations.

When you return an incomplete type, X f2(); then you say your caller must have a complete type specification for X. They need it to create LHS or temporary objects at the calling site.

Likewise, if you accept an incomplete type, the caller must construct an object as a parameter. Even if the object is returned from the function as another incomplete type, the calling site needs a full declaration. Namely:

class X;  // forward for two legal declarations 
X returnsX();
void XAcceptor(X);

XAcepptor( returnsX() );  // X declaration needs to be known here

I think there is an important principle that a header should provide enough information to use it without relying on other headers. This means that when using any function declared by the header, the header should be able to be included in the compilation unit without causing compiler errors.

except

  1. If external dependence is expected behavior. Rather than using conditional compilation, use well documented requirements that provide their own header declaration X. This is an alternative to using ifdefs and is a useful way to introduce simulations or other variants.

  2. The important difference is that some template technologies explicitly don't require you to instantiate them, just mention that someone won't talk to me.

#3 building

So far, there is no answer to describe when forward declarations of class templates can be used. So that's it.

Class template forwarding can be declared as:

template <typename> struct X;

according to Accepted answers Structure,

This is what you can and cannot do.

What you can do with incomplete types:

  • Declare that a member is a pointer or reference to an incomplete type in another class template:

    template <typename T> class Foo { X<T>* ptr; X<T>& ref; };
  • A pointer or reference that declares a member as one of its incomplete instances:

    class Foo { X<int>* ptr; X<int>& ref; };
  • Declare to accept / return a function template or member function template of an incomplete type:

    template <typename T> void f1(X<T>); template <typename T> X<T> f2();
  • Declare a function or member function that accepts or returns one of its incomplete instances:

    void f1(X<int>); X<int> f2();
  • Define a function template or member function template that accepts / returns pointers / references of an incomplete type (but does not use its members):

    template <typename T> void f3(X<T>*, X<T>&) {} template <typename T> X<T>& f4(X<T>& in) { return in; } template <typename T> X<T>* f5(X<T>* in) { return in; }
  • Define a function or method that accepts / returns a pointer / reference to one of its incomplete instances (but does not use its members):

    void f3(X<int>*, X<int>&) {} X<int>& f4(X<int>& in) { return in; } X<int>* f5(X<int>* in) { return in; }
  • Use it as the base class for another template class

    template <typename T> class Foo : X<T> {} // OK as long as X is defined before // Foo is instantiated. Foo<int> a1; // Compiler error. template <typename T> struct X {}; Foo<int> a2; // OK since X is now defined.
  • Use it to declare members of another class template:

    template <typename T> class Foo { X<T> m; // OK as long as X is defined before // Foo is instantiated. }; Foo<int> a1; // Compiler error. template <typename T> struct X {}; Foo<int> a2; // OK since X is now defined.
  • Use this type to define a function template or method

    template <typename T> void f1(X<T> x) {} // OK if X is defined before calling f1 template <typename T> X<T> f2(){return X<T>(); } // OK if X is defined before calling f2 void test1() { f1(X<int>()); // Compiler error f2<int>(); // Compiler error } template <typename T> struct X {}; void test2() { f1(X<int>()); // OK since X is defined now f2<int>(); // OK since X is defined now }

What incomplete types cannot do:

  • Use one of its instantiations as the base class

    class Foo : X<int> {} // compiler error!
  • Use one of its instantiations to declare a member:

    class Foo { X<int> m; // compiler error! };
  • Use one of its instantiations to define a function or method

    void f1(X<int> x) {} // compiler error! X<int> f2() {return X<int>(); } // compiler error!
  • Using a method or field that is one of its instantiations, you actually attempt to dereference a variable with an incomplete type

    class Foo { X<int>* m; void method() { m->someMethod(); // compiler error! int i = m->someField; // compiler error! } };
  • Create an explicit instantiation of a class template

    template struct X<int>;

#4 building

I just want to add one important thing that you can do using the forwarding class that is not mentioned in Luc Touraille's answer.

What you can do with incomplete types:

Defines a function or method that accepts / returns a pointer / reference of an incomplete type and forwards that pointer / reference to another function.

void  f6(X*)       {}
void  f7(X&)       {}
void  f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }

A module can be passed to another module through an object of a forward declared class.

#5 building

As long as you don't need definitions, such as pointers and references, you can avoid using forward declarations. That's why most of the time you see them in the headers, and implementation files usually extract the corresponding defined headers.