Explicit Template Instantiation - When Is It Used

Explicit template instantiation - when is it used?

Directly copied from https://docs.microsoft.com/en-us/cpp/cpp/explicit-instantiation:

You can use explicit instantiation to create an instantiation of a templated class or function without actually using it in your code. Because this is useful when you are creating library (.lib) files that use templates for distribution, uninstantiated template definitions are not put into object (.obj) files.

(For instance, libstdc++ contains the explicit instantiation of std::basic_string<char,char_traits<char>,allocator<char> > (which is std::string) so every time you use functions of std::string, the same function code doesn't need to be copied to objects. The compiler only need to refer (link) those to libstdc++.)

When would you use template explicit instantiation?

One of the use cases is to hide definitions from the end-user.

tpl.h:

template<typename T>
void func(); // Declaration

tpl.cpp:

template<typename T>
void func()
{
// Definition
}

template void func<int>(); // explicit instantiation for int
template void func<double>(); // explicit instantiation for double

main.cpp

#include "tpl.h"
int main()
{
func<double>(); // OK
func<int>(); // OK
// func<char>(); - Linking ERROR
}

How template explicit instantiation works and when?

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.

Automating explicit template instantiation

If the compiler sees that code isn't referred to, even for static initialization with side effects, it can eliminate it, and I think that's the case in your example. It can "prove" those class instantiations are not used, and so the side effects are lost.

For a non-standard solution, but one that works on g++ (and presumably clang, but not tested) is to mark your static data members with the "used" attribute:

template <Enum Value>
struct Instantiate
{
using Function = int (Type::*)() const;
static constexpr Function f1 __attribute__((used)) = &Type::get<Value>;

// many more member-functions
};

Update

Reviewing the standard, the wording seems like I got it exactly backwards:

"If an object of static storage duration has initialization or a
destructor with side effects, it shall not be eliminated even if it
appears to be unused, except that a class object or its copy may be
eliminated as specified in ..."

So I've had this in my head for decades, and now I'm uncertain as to what I was thinking. :) But it seems related, given the attribute helps. But now I have to learn what's going on.

Explicit template instantiation for a range of template parameters in C++

Let's start with a slight shift in perspective. The goal is to instantiate certain instances of a given template. Note that I dropped the word "explicit" – while there will be an explicit instantiation, it need not be of the template in question. Rather, I would explicitly instantiate a helper template that will implicitly instantiate the desired template.

The helper template will use a parameter pack to accommodate an arbitrary number of arguments. This would be reasonably straight-forward if the goal was to simply list numbers. However, there is also a desire to be able to specify a min and max. To handle this case, I'll support std::integer_sequence as a template argument, which I'll handle via partial specialization.

// Start with a template that will not be defined; we will define only a
// specialization of this.
// The `C` parameter allows this to be applied to more templates than just `S`.
// The other parameters will make more sense for the specialization.
template<template<int> typename C, int ADD, class T> struct force_instantiation_impl;

// Partially specializing the template allows access to the numbers comprising the
// `integer_sequence` parameter.
// The ADD parameter will be added to each number in the sequence (will be helpful later).
template<template<int> typename C, int ADD, int... Ints>
struct force_instantiation_impl<C, ADD, std::integer_sequence<int, Ints...>> {
// Implicitly instantiate the desired templates.
std::tuple<C<ADD + Ints>...> unused;
};

Here, parameter pack expansion is used to iterate over all desired template arguments. This plays the role given to the loop in the "wrong code" of the question. Of course, we still have to get the indices for a given minimum and maximum.

For convenience, I would provide another layer of helper templates, an "interface" layer, if you will. The first interface helper allows specifying just the template, the minimum, and the maximum. Instantiating this helper will cause the desired templates to be instantiated.

// Instantiates C<I> for I ranging from MIN to MAX.
// MAX must be at least as large as MIN.
template<template<int> typename C, int MIN, int MAX>
struct force_instantiation_range {
// Check the assumption.
static_assert(MIN <= MAX);
// Make an integer sequence from 0 to (MAX-MIN) for the template argument.
// When MIN is added to each element, the sequence will go from MIN to MAX.
force_instantiation_impl<C, MIN, std::make_integer_sequence<int, MAX-MIN+1>> unused;
};

The other interface helper allows simply listing the desired arguments. To some extent, the purpose of this helper is to hide the complexities introduced by supporting a range of arguments. (If it were not for that support, the parameters to this template could have been the parameters to force_instantiation_impl.)

// Instantiates C<I> for the given I's.
template<template<int> typename C, int... Ints>
struct force_instantiation_list {
force_instantiation_impl<C, 0, std::integer_sequence<int, Ints...>> unused;
};

And that's it. It might look like a lot of code because of the comments, but it's actually reasonably short. There are three struct template definitions, each with a single member. To put this to use, explicitly instantiate one of the interface helpers.

// Forgive me, I'm changing the pre-processor directives to type-safe constants.
constexpr int MIN_I = 1;
constexpr int MAX_I = 16;
template struct force_instantiation_range<S, MIN_I, MAX_I>;

Equivalently, the arguments could be explicitly listed.

template struct force_instantiation_list<S, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16>;

Admittedly, I did shift the question a bit. If you really need explicit instantiations directly of your template, then this approach will not work. If that is the case, you might have to rely on the pre-processor, which can be a nightmare to get right. Fortunately, Boost.Preprocessor has already taken care of the most painful of the details. I'd take advantage of that.

Explicit template instantiation example

Maybe this help you to unsderstand.

From the C++ 20 (13.9.2 Explicit instantiation)

2 The syntax for explicit instantiation is:

explicit-instantiation:
externopt template declaration

There are two forms of explicit instantiation: an explicit
instantiation definition and an explicit instantiation declaration. An
explicit instantiation declaration begins with the extern keyword.

So this line

extern template struct A < int > ;

is an explicit instantiation declaration of the class specialization struct A<int>.

Explicit C++ template instantiation with clang

This program is well-formed

Quoting [temp.explicit]/1
[emphasis mine]:

A class, function, variable, or member template specialization can be
explicitly instantiated from its template. A member function,
member class or static data member of a class template can be
explicitly instantiated from the member definition associated with
its class template
. [..]

And, quoting [temp.explicit]/9 [emphasis mine]:

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
.

Thus, in the OPs example, the explicit instantiation definition of stack<T> will not include an explicit instantiation definition of the constructor, as the explicit instantiation definition of stack<T> is placed before providing the definition of it constructor via the .tpp include.

Quoting [temp.point]/8 [emphasis mine]:

A specialization for a function template, a member function template, or of a member function or static data member of a
class template may have multiple points of instantiations within a
translation unit
, and in addition to the points of instantiation
described above, 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.
A specialization
for a class template has at most one point of instantiation within a
translation unit. A specialization for any template may have points of
instantiation in multiple translation units. If two different points
of instantiation give a template specialization different meanings
according to the one-definition rule, the program is ill-formed, no
diagnostic required.

Thus, for the cases wheres stack.cpp includes two different points of instantiations, and where one is before and one is after the inclusion of stack.tpp, then the program is ill-formed.

However, here it becomes a bit tricky, as the points of instantiations depends on how the class template and its member function (/constructor) is used. As covered by [temp.explicit]/9 quoted above, the explicit instantiation of stack<T> will not result in an explicit instantiation definition of its constructor, and we will instead need to fall back on [temp.point] for details, particularly clauses 1, 2 and 4, on when its use context will lead to an instantiation point before the inclusion of stack.tpp.

The stand-alone example in the question is not covered by any of these cases, and thus the program us not ill-formed.

GCC vs clang: seemingly different instantiation behaviour?

Can anyone tell me how I can get the constructor symbols for my class template using clang++?

As the constructor is never used, it should never (need to) be instantiated at all, but it seems as if (from the OPs symbol dump) GCC does so anyway (which is not illegal) whereas clang does not. If one where to use/refer to the constructor after the inclusion of track.tpp, then both GCC and clang naturally instantiates it (for the particular specialization that is used), as they are then required to do so.



Related Topics



Leave a reply



Submit