7

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.

Maestro
  • 2,512
  • 9
  • 24

1 Answers1

6

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.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Hmm, so the explicit instantiation is not ill-formed necessarily? – cigien Nov 12 '20 at 22:20
  • @cigien yes, I believe that hypothetically it may succeed as explained in the footnote, but in practice you will not see this happen – Brian Bi Nov 12 '20 at 22:23
  • @cigien Actually, I changed my mind. I think that strategy is no longer allowed in C++20, since most members of `std::vector` are now `constexpr`. – Brian Bi Nov 12 '20 at 22:25
  • I'm not sure why the `constexpr`ness of the members would prevent that strategy. It's still allowed to split the declaration and definition if wanted I think. – cigien Nov 12 '20 at 22:35
  • @cigien But you can't defer the definition to the end of the TU if it's `constexpr`. I mean, you could, but it would result in that function not being usable in constant expressions at all, since all uses would necessarily precede the definition. I don't think the standard can possibly be interpreted in a way that would make that result conforming. – Brian Bi Nov 12 '20 at 22:46
  • Hmm, no, and if could be interpreted that way, it would be considered a wording defect. Nice answer, looks good to me. – cigien Nov 12 '20 at 22:56
  • You are really genius! because the default constructor of `Foo` is defined as a deleted function then the compiler doesn't instantiate it consequently it doesn't need the default constructor of `Bar`. To ensure what I'm saying: just add a user-defined default constructor to `Foo` that is explicitly defined (not marked as default so not synthesized by the compiler) then the instantiation definition will fail as with `vector`: template class Foo { public: Foo(){} // Foo() = default As you can see now the previous code doesn't work with `Foo – Maestro Nov 13 '20 at 00:01
  • If I define `Foo()=default` will be deleted too hence the code wrks fine with `Foo doesn't work because it has a default ctor defined (not deleted ) thus it needs thd default one of its element type. – Maestro Nov 13 '20 at 00:07