Here is an exercise from C++ primer 5th edition:
"Exercise 16.26: Assuming NoDefault
is a class that does not have a default constructor, can we explicitly instantiate vector<NoDefault>
? If not, why not?"
Here is my guess:
Yes we can instantiate it:
template <typename T>
class Foo
{
public:
void func(){cout << x_.value_ << endl;}
private:
T x_;
};
class Bar
{
public:
Bar(int x) : value_(x){}
void print(){}
private:
int value_{};
template <class T>
friend class Foo;
};
extern template class Foo<Bar>; // instantiation declaration
template class Foo<Bar>; // instantiation definition
int main()
{
// Foo<Bar> f;
}
The code works fine but if I uncomment the line in main I get error as expected because Bar
is not default-constructible.
If I use the same class Bar
as an element type for std::vector
it doesn't work:
extern template class vector<Bar>; // declaration ok
template class vector<Bar>; // instantiation: doesn't work?!
So why my Foo<Bar>
instantiation works but not vector<Bar>
?
What looks to me is that it is logical in vector<Bar>
not to work because an explicit instantiation definition instantiates all the members (even the ones not used) of the class template; and in this example among them the default constructor of Foo<Bar>
that implies a default ctor
of its element type Bar
; the latter doesn't provide one; after all Foo<Bar>()
normally is declared as a deleted member function because x_
doesn't have a default constructor. SO I don't know why Foo<Bar>
definition works?! Thank you.
In the Standard, the [temp.explicit] section explains what happens in an explicit instantiation. In particular, p12 provides that:
An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is an explicit instantiation definition of only those members that have been defined at the point of instantiation.
Now, std::vector<T>
has a constructor that takes an integer n
and initializes the vector with n
value-initialized T
's. It can be assumed that the definition of this constructor is somewhere inside the <vector>
header (see Why can templates only be implemented in the header file?). So the explicit instantiation definition of std::vector<Bar>
will instantiate that constructor with T
= Bar
.
Because this is an explicit instantiation, it is not only the signature of that constructor that is instantiated, but its entire body as well. This must, somewhere, include a call to the default constructor of Bar
(possibly as part of another function that it calls, which would also be instantiated at this point), so a compilation error occurs as part of the explicit instantiation definition of std::vector<Bar>
. Note that if you were implicitly instantiating std::vector<Bar>
, it would only instantiate (roughly speaking) the signatures of the member functions. This is why it's legal to actually define and use std::vector<Bar>
objects, as long as you don't call any function that requires the default constructor of Bar
to exist.
The reason why an explicit instantiation definition of Foo<Bar>
succeeds is that, when Foo<Bar>
is instantiated, the compiler marks its default constructor as deleted (this always happens whenever there is a non-default-constructible non-static member). It therefore does not at any point attempt to compile any code that requires the default constructor of Bar
, and no error occurs.