How template explicit instantiation works and when?
Asked Answered
R

1

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.

Revealment answered 12/11, 2020 at 21:39 Comment(0)
S
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.

Strident answered 12/11, 2020 at 22:13 Comment(8)
Hmm, so the explicit instantiation is not ill-formed necessarily?Hizar
@Hizar yes, I believe that hypothetically it may succeed as explained in the footnote, but in practice you will not see this happenStrident
@Hizar 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.Strident
I'm not sure why the constexprness of the members would prevent that strategy. It's still allowed to split the declaration and definition if wanted I think.Hizar
@Hizar 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.Strident
Hmm, no, and if could be interpreted that way, it would be considered a wording defect. Nice answer, looks good to me.Hizar
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<Bar>: template <typename T> class Foo { public: Foo(){} // Foo() = default As you can see now the previous code doesn't work with Foo<BarRevealment
If I define Foo()=default will be deleted too hence the code wrks fine with Foo<Bar but Foo(){} wil cause an eror because it will be instantiated thus needs Bar default ctor whis is missing consequently an error. I think this is why vector<Bar> doesn't work because it has a default ctor defined (not deleted ) thus it needs thd default one of its element type.Revealment

© 2022 - 2024 — McMap. All rights reserved.