Why Can't a Forward Declaration Be Used for a Std::Vector

Why can't a forward declaration be used for a std::vector?

The compiler needs to know how big "B" is before it can generate the appropriate layout information. If instead, you said std::vector<B*>, then the compiler wouldn't need to know how big B is because it knows how big a pointer is.

std::vector on forward declared type

This is undefined behavior in C++14 and earlier; well-defined in C++17 (if it's 17).

[res.on.functions]/p2, bullet 2.7:

In particular, the effects are undefined in the following cases:

  • [...]
  • if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for
    that component.

In C++14 and earlier, std::vector does not "specifically allow" this. So the behavior is undefined.

For C++17, N4510, adopted at the committee's May 2015 meeting, relaxes this rule for vector, list, and forward_list.

How come I can use a forward-declared class in a std::vector?

Actually, you can't.

Just because your program compiles (which is down to facts of the underlying implementation) does not mean it is valid.

There are times other than declaring a T* or a T& at which you may use a forward declaration; it's just that this is not one of them.

Forward declare a standard container?

Declaring vector in the std namespace is undefined behavior. So, your code might work, but it also might not, and the compiler is under no obligation to tell you when your attempt won't work. That's a gamble, and I don't know that avoiding the inclusion of a standard C++ header is worth that.

See the following comp.std.c++.moderated discussion:

forward declaring std::vector. Works, but is it legal and standard compliant?

Declaration of std::vector works with forward declared classes?

This is an interesting topic (at least to me) and applies to other std containers.

Originally the standard made it undefined behaviour to instantiate a container of an incomplete type. However implementations did not disallow it. This was in all likelihood not deliberate, but merely a side-effect of the fact that elements in (for example the vector) are stored in a memory location that is referenced by a pointer.

Thus the size of an element does not need to be known until an element is actually required - during the instantiation of a member function of the vector.

Here is a starting point for research if you'd like to explore further:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4056.html

When can I use a forward declaration?

Put yourself in the compiler's position: when you forward declare a type, all the compiler knows is that this type exists; it knows nothing about its size, members, or methods. This is why it's called an incomplete type. Therefore, you cannot use the type to declare a member, or a base class, since the compiler would need to know the layout of the type.

Assuming the following forward declaration.

class X;

Here's what you can and cannot do.

What you can do with an incomplete type:

  • Declare a member to be a pointer or a reference to the incomplete type:

    class Foo {
    X *p;
    X &r;
    };
  • Declare functions or methods which accept/return incomplete types:

    void f1(X);
    X f2();
  • Define functions or methods which accept/return pointers/references to the incomplete type (but without using its members):

    void f3(X*, X&) {}
    X& f4() {}
    X* f5() {}

What you cannot do with an incomplete type:

  • Use it as a base class

    class Foo : X {} // compiler error!
  • Use it to declare a member:

    class Foo {
    X m; // compiler error!
    };
  • Define functions or methods using this type

    void f1(X x) {} // compiler error!
    X f2() {} // compiler error!
  • Use its methods or fields, in fact trying to dereference a variable with incomplete type

    class Foo {
    X *m;
    void method()
    {
    m->someMethod(); // compiler error!
    int i = m->someField; // compiler error!
    }
    };

When it comes to templates, there is no absolute rule: whether you can use an incomplete type as a template parameter is dependent on the way the type is used in the template.

For instance, std::vector<T> requires its parameter to be a complete type, while boost::container::vector<T> does not. Sometimes, a complete type is required only if you use certain member functions; this is the case for std::unique_ptr<T>, for example.

A well-documented template should indicate in its documentation all the requirements of its parameters, including whether they need to be complete types or not.

Forward declaration of objects with STL containers

The relevant rules for the standard library types are in [res.on.functions]:

In particular, the effects are undefined in the following cases: [...] if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.

This:

vector<A> Av;

is fine. std::vector is allowed to be instantiated with an incomplete type, as long as it becomes complete before you use any of the members. There is an explicit exception for this in the standard in [vector.overview]:

An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness
requirements 17.6.3.5.1. T shall be complete before any member of the resulting specialization of vector
is referenced.

There is similar wording for std::list and std::forward_list.

This:

map<int, A> Am;

is ill-formed. std::map requires a complete type at point of instantiation as per the first quote. There is no exception for this container in the way that there is for vector.

This:

pair<int, A> Ap;

cannot possibly ever work, since pair is just a simply struct with two members. In order to have a member of type A, you need a complete type.

Why does std::vector work with incomplete types in class definitions?

Standard says (draft N3690; this is post C++11, pre C++14):

[res.on.functions]

1 In certain cases (replacement functions, handler functions,
operations on types used to instantiate standard library template
components), the C++standard library depends on components supplied by
a C++program. If these components do not meet their requirements,
the Standard places no requirements on the implementation.

2 In particular, the effects are undefined in the following cases:

— if an incomplete type (3.9) is used as a template argument when
instantiating a template component, unless specifically allowed for
that component.

Given that standard places no requirements, and effects are undefined (as far as I can tell, this is same as undefined behaviour), there is no expectation for the instantiation to "not work" any more than there is expectation for it to (appear to) "work".


Since C++17, the requirement was relaxed and std::vector does not require the value type to be complete, if used with appropriate allocator (the default allocator is appropriate). (This freedom does not extend to using all member functions; they have additional requirements).

Standard quote (current draft):

[vector.overview]

An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements.
T shall be complete before any member of the resulting specialization of vector is referenced.

[allocator.requirements.completeness]

If X is an allocator class for type T, X additionally meets the allocator completeness requirements if, whether or not T is a complete type:

  • X is a complete type, and
  • all the member types of allocator_­traits other than value_­type are complete types.

[default.allocator]

All specializations of the default allocator meet the allocator completeness requirements ([allocator.requirements.completeness]).

forward declaration with vector of class type - pointer to incomplete class type not allowed

You can't forward declare members.

Instead, bar.cpp should #include both foo.h and bar.h. Problem solved.

In general, if you use the sequence:

  • Forward declare all class types
  • Define all class types
  • Bodies of class members

everything will be fine.

Should one use forward declarations instead of includes wherever possible?

The forward-declaration method is almost always better. (I can't think of a situation where including a file where you can use a forward declaration is better, but I'm not gonna say it's always better just in case).

There are no downsides to forward-declaring classes, but I can think of some downsides for including headers unnecessarily:

  • longer compilation time, since all translation units including C.h will also include A.h, although they might not need it.

  • possibly including other headers you don't need indirectly

  • polluting the translation unit with symbols you don't need

  • you might need to recompile source files that include that header if it changes (@PeterWood)



Related Topics



Leave a reply



Submit