This code is surely ill-formed, because Foo
is specialized after an instantiation point:
template
struct Foo {
int a;
};
Foo
Fortunatly, yes this is well-defined. For the exact same reason this is well-defined:
struct A;
class Foo { A* value; };
Foo foo; // note A is incomplete here
struct A {};
and this is ill-formed:
struct A;
template <class T> class Foo { T value; }; // error: 'Foo<T>::value' has incomplete type
Foo<A> foo; // note A is incomplete here
struct A {};
Assuming we only have one translation unit, [temp.point] rules out your quote as a possible source of ill-formedness
A specialization for a class template has at most one point of instantiation within a translation unit.
Instead, the problem with the first snippet is [temp.expl.spec]
If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.
The second snippet is well-formed, there is no requirement that template parameters need to have complete type.
The third snippet is ill-formed, new T
requires that T
be a complete type. A slight catch here is that the definition of the constructor is implicitly instantiated at Foo<A> foo;
. If however, the snippet is changed to
struct A;
template <typename T>
struct Foo {
Foo() {
new T;
}
};
using FooA = Foo<A>;
struct A {};
Then the definition of the constructor isn't instantiated and will therefore be well-formed. [temp.inst]
The implicit instantiation of a class template specialization causes
- the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and [...]
The fourth snippet is ill-formed because members need to have complete type. [class.mem]
The type of a non-static data member shall not be an incomplete type [...]
struct A;
template <typename> class Foo { };
Foo<A> foo; // note A is incomplete here
struct A {};
Foo<A>
only depends on the name of A
not its complete type.
So this is well-formed; however, this kind of thing can still break (become ill-formed) yet compile in every compiler you test.
First, we steal is_complete. Then we do this:
struct A;
template <class T> class Foo {
enum{ value = is_complete<T>::value };
};
Foo<A> foo; // note A is incomplete here
struct A {};
We are ok, despite this:
[...] for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. [...]
because that clause does not apply to template classes. Here, the only instantiation of the template class is fine.
Now, if in another file you have:
struct A {};
Foo<A> foo2;
your program is ill formed.
However, in the one-file case:
struct A;
template <class T> class Foo {
enum{ value = is_complete<T>::value };
};
Foo<A> foo; // note A is incomplete here
struct A {};
Foo<A> foo2; // ill-formed
your code is fine. There is one point of instantiation for Foo<A>
in a given compilation unit; the second one is a reference to the first point of instantiation.
Both the one and two file versoins will almost certainly compile in C++ compilers with no errors or warnings.
Some compilers memoize template instantiations even from one compilation unit to anther; Foo<A>
will have a ::value
that is false
even after foo2
is created (with a complete A
). Others will have two different Foo<A>
s in each compilation unit; its methods will be marked inline (and be different), the size of the classes may disagree, and you'll get cascades of ill formed program problems.
Finally, note that many types in std
require that their template arguments are complete in older versions of C++ (including c++11: “17.6.4.8 Other functions (...) 2. the effects are undefined in the following cases: (...) In particular - if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component” -- copied from boost incomplete container docs). To be concrete, std::vector<T>
used to require T
to be complete.
By c++17 that has changed for std::vector:
[vector.overview]/3
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.
Now, even prior to c++17, most implementations of std::vector<T>
are fine with an incomplete T
until you try to use a method (including many of its constructors or the destructor), but the standard stated that T
must be complete.
This actually gets in the way of some unuseful code, like having a function type that returns vectors of its own type1. Boost has a library to solve this problem.
template <typename T>
struct Foo {
Foo() {
new T;
}
};
The body of Foo<T>::Foo()
is only instantiated "when called". So T
's lack of completion has no impact until Foo::Foo()
is called.
Foo<A> foo;
^^ will fail to compile with a non-complete A
.
using foo_t = Foo<A>;
^^ will compile, and cause no problems.
using foo_t = Foo<A>;
struct A {};
foo_t foo;
also no problems. The body of foo_t::foo_t
gets instantiated when we try to construct a foo_t
, and all definitions match.
1 Can you say state machine transition function?