Separating definition/instantiation of template cl

2019-05-30 08:41发布

问题:

The (not so new anymore) C++11 standard introduced the extern keyword for templates. Its purpose is to tell the compiler that a template should not be instantiated at the point of usage, but that it will be instantiated in another translation unit (and thus there will be an instantiation available at link time) - at least AFAIK.

Now, even in the pre-C++11 era we used something similar to separate the declaration/definition of template classes from its instantiation in order to speed up compilation, e.g. like so:

point.h: class definition

template <int dim> struct Point {
  ...
  void foo();
  ...
};

point.cpp: method definitions

#include "point.h"
template <int dim>
void Point<dim>::foo() {
  ...
}

point_2d.cpp: class instantiation (2D version)

#include "point.cpp"
template struct Point<2>;

point_3d.cpp: class instantiation (3D version)

#include "point.cpp"
template struct Point<3>;

main.cpp: usage of 2D and 3D points

#include "point.h"
int main(int, char**) {
    Point<2> p;
    p.foo();
}

Now I am wondering:

  • Is our approach valid C++ (03 or 11) code or are we just lucky it worked?
  • With C++11, would we be able to achieve the same thing by including point.cpp in main.cpp and declaring extern template <int dim> struct Point;?

回答1:

Your approach is a valid C++ code and should work both in C++03 and C++11. It is called explicit instantiation:

template struct Point<2>;

While applying to class templates it explicitly instantiates all members of a class template for some template argument list in the current translation unit. With this approach it is simple to create the libraries of templates for which the set of possible template arguments is known in advance (as in your case when Point can be 2D and 3D but not 1D, 4D etc.).

In C++11 when you add to explicit instantiation directive keyword extern:

extern template struct Point<2>;

it becomes a declaration, not a definition. The behavior of such thing is similar to usual extern keyword for variables. Explicit template instantiation declaration can be used together with explicit instantiation in the following manner:

// point.h

template <int dim> struct Point {
  ...
  void foo();
  ...
};

extern template struct Point<2>;
extern template struct Point<3>;

#include "point.hpp"


// point.hpp

#include "point.h"

template <int dim>
void Point<dim>::foo() {
  ...
}

// point.cpp

#include "point.hpp"

template struct Point<2>;
template struct Point<3>;

With such code you achieve the same result as with your one but additionally you allow the users of your code to use Point<1>, Point<4> and other specializations of a Point class template if they want. Without extern template struct Point<2>; and extern template struct Point<3>; directives Point class template would be implicitly instantiated in the users code even for template arguments 2 and 3 that reduces the meaning of explicit instantiation (template struct Point<2>; and template struct Point<2>;).



回答2:

C++11 is using extern to tell the compiler to not to instantiate a template, and the syntax is using a concrete type, unlike the suggested syntax in the question:

extern template struct Point<2>; 

With C++03 the compiler must instantiate the template whenever it observes Point<2> in a translation unit and with C++11 it knows that it must not, when coupled with the extern keyword.

To your question, what you did in C++03 was to separate the definition of the template into a separate header file (with a cpp suffix, see below), and that approach will still work with C++11:

#include "point.ipp"

extern template struct Point<2>; // instantiated elsewhere

int main(int, char**) {
    Point<2> p;
    p.foo();
}

Subjectively, I also dislike the cpp suffixes for template header files so much that I wanted to bring it to your attention. It is confusing and misleading, especially when one sees a cpp file included in another cpp file.

Consider using an ipp or ixx as the file extension coupled with hpp and hxx respectively, it's more clear that the file includes the definition/implementation of a particular template.



回答3:

Is our approach valid C++ (03 or 11) code or are we just lucky it worked?

Your usage is valid, but not canonical. The more common C++98 style would group the explicit instantiation definitions together with the implementation, in the point.cpp file.

// point.h

template <int dim> struct Point {
  ...
  void foo();
  ...
};

 

// point.cpp

#include "point.h"

template <int dim>
void Point<dim>::foo() {
  ...
}

// Now the implementation is available, so this is the time to use it:
template struct Point<2>;
template struct Point<3>;

With C++11, would we be able to achieve the same thing by including point.cpp in main.cpp and declaring extern template <int dim> struct Point;?

You should never #include a .cpp file. The canonical style is to put the function template implementation in a header file, either directly in point.h or in a template-implementation file named e.g. point.hpp.

The benefit added by C++11 is that you can have fast compilation times and also use Point<4> by implicit specialization. However, putting the implementation in a header with extern specializations won't speed compilation Point<2> and Point<3>.

You can't literally say extern template <int dim> struct Point;. The header needs to list all the specializations:

extern template struct Point<2>;
extern template struct Point<3>;

Delegating all specializations to somewhere else was the intended purpose of export templates. This was a C++98 feature which proved to be too difficult to implement, and so it was removed from the C++11 standard.