2021-10-03

9: C++ Template: Instantiation Explicit or Implicit, Inlining

<The previous article in this series | The table of contents of this series | The next article in this series>

An "unresolved external symbol" error? No such thing as a "template class or function". Let me distinguish between instantiation and inlining.

Topics


About: C++

The table of contents of this article


Starting Context


  • The reader has a basic knowledge on C++, even if he or she doesn't accurately understand its some widely-misrepresented elements.

Target Context


  • The reader will understand what template is and how to use it.

Orientation


This article deals with both GCC and Visual C++ (there are some peculiarities per compiler).

Java generics has been addressed in an article of another series.

There is an article that clarifies that class in C++ is not any object, but a blueprint.

There will be an article that examines the appalling-ness of the 'inline' specifications.

There will be an article that solves a "there are no arguments to ‘%method name%’ that depend on a template parameter, so a declaration of ‘%method name%’ must be available [-fpermissive]" compile error.


Main Body


1: Any Class Template Is Not Any Class, As Any Function Template Is Not Any Function


Hypothesizer 7
I had added a class template like this, into my project.

'theBiasPlanet/tests/templatesTest1/ClassA.hpp'

@C++ Source Code
#ifndef __theBiasPlanet_tests_templatesTest1_ClassA_hpp__
#define __theBiasPlanet_tests_templatesTest1_ClassA_hpp__

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			template <typename T> class ClassA {
				private:
					T i_memberVariableA0;
				public:
					ClassA ();
					template <typename U> ClassA (U a_argument0);
					T methodA0 ();
					template <typename U> U methodA1 (U a_argument0);
					template <typename U, typename ... V> U methodA2 (U a_argument0, V ... a_remainingArguments);
			};
		}
	}
}

#endif

'theBiasPlanet/tests/templatesTest1/ClassA.cpp'

@C++ Source Code
#include "theBiasPlanet/tests/templatesTest1/ClassA.hpp"

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			template <typename T> ClassA <T>::ClassA () {
			}
			
			template <typename T> template <typename U> ClassA <T>::ClassA (U a_argument0) {
			}
			
			template <typename T> T ClassA <T>::methodA0 () {
				return i_memberVariableA0;
			}
			
			template <typename T> template <typename U> U ClassA <T>::methodA1 (U a_argument0) {
				return a_argument0;
			}
			
			template <typename T> template <typename U, typename ... V> U ClassA <T>::methodA2 (U a_argument0, V ... a_remainingArguments) {
				return a_argument0;
			}
		}
	}
}

"That code compiled fine", I thought, because there was no compile error.

So, I had added a class that used the template like this.

'theBiasPlanet/tests/templatesTest1/ClassB.hpp'

@C++ Source Code
#ifndef __theBiasPlanet_tests_templatesTest1_ClassB_hpp__
#define __theBiasPlanet_tests_templatesTest1_ClassB_hpp__

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			class ClassB {
				public:
					int methodB0 ();
			};
		}
	}
}

#endif

'theBiasPlanet/tests/templatesTest1/ClassB.cpp'

@C++ Source Code
#include "theBiasPlanet/tests/templatesTest1/ClassB.hpp"
#include "theBiasPlanet/tests/templatesTest1/ClassA.hpp"

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			int ClassB::methodB0 () {
				ClassA <int> l_classA;
				return l_classA.methodA0 ();
			}
		}
	}
}

That code does not link, with errors, "undefined reference to `theBiasPlanet::tests::templatesTest1::ClassA<int>::ClassA()'" and "undefined reference to `theBiasPlanet::tests::templatesTest1::ClassA<int>::methodA0()'" . . .

Well, by analogy with Java generics, I had assumed that the method templates, 'template <typename T> ClassA <T>::ClassA ()' and 'template <typename T> T ClassA <T>::methodA0 ()', existed in the object file of 'ClassA.cpp', but that was not the case.

Let me take this to heart: any template never exists in any object file.

It is very important to use terms rigorously, and in this case, one should never use a sloppy term like "template function" or "template class": "template function" implies being a function and being a function implies existing in an object file, if it is non-inline, I mean.

Of course, a term like 'derived-from-template function' is plausible, and even "template function" as its abbreviation is passable, but the problem is that "template function" is frequently used meaning 'function template'.


2: Clarification About the Term, 'inline', as a Preparation


Hypothesizer 7
Let me clarify the term, 'inline', as I use it hereafter to make it represent a key concept.

Well, I deem the specifications on 'inline' appalling (I will discuss how it is so, in an article).

Anyway, 'inlining' I use here means really inlining, which is to really replace a function call with the function body. . . . In fact, it is really annoying that I have to use a qualification like "really" there.

This does really 'inlining' in GCC, by virtue of "__attribute__((always_inline))".

'theBiasPlanet/tests/templatesTest1/ClassK.hpp'

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_templatesTest1_ClassK_hpp__
#define __theBiasPlanet_coreUtilitiesTests_templatesTest1_ClassK_hpp__

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace templatesTest1 {
			class ClassK {
				private:
					int i_memberVariableK0;
				public:
					inline ClassK () __attribute__((always_inline));
					inline int methodK0 () __attribute__((always_inline));
					int methodK1 ();
			};
		}
	}
}

#endif

'theBiasPlanet/tests/templatesTest1/ClassK.ipp'

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/templatesTest1/ClassK.hpp"
#include <iostream>

using namespace ::std;

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace templatesTest1 {
			inline ClassK::ClassK () {
			}
			
			inline int ClassK::methodK0 () {
				cout << "### the version: 2" << endl << flush;
				return i_memberVariableK0;
			}
		}
	}
}

'theBiasPlanet/tests/templatesTest1/ClassH.hpp'

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_templatesTest1_ClassH_hpp__
#define __theBiasPlanet_coreUtilitiesTests_templatesTest1_ClassH_hpp__

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace templatesTest1 {
			class ClassH {
				public:
					int methodH0 ();
			};
		}
	}
}

#endif

'theBiasPlanet/tests/templatesTest1/ClassH.cpp'

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/templatesTest1/ClassH.hpp"
#include "theBiasPlanet/coreUtilitiesTests/templatesTest1/ClassK.ipp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace templatesTest1 {
			int ClassH::methodH0 () {
				ClassK l_classK;
				return l_classK.methodK0 ();
			}
		}
	}
}

Note that ".ipp" is not any typo: I use it because the file is not something to be compiled.

If "__attribute__((always_inline))" is removed, it will not really do 'inlining', at least with my GCC or Visual C++.

What is appalling is that such a compiler-dependent matter cannot be dismissed just as an optimization matter that can be trusted to the compiler discretion, as will be discussed in an article.

It is important to understand that any 'inline' function does not exist as any (objective) object.


3: Template Is a Blueprint, Which Is Not Any (Objective) Object


Hypothesizer 7
Any template never exists in any object file because template is a blueprint, which is not any object.

"Any blueprint is a kind of object because it exists in the world somehow, for example, on a sheet of paper."? . . . Well, I think that any blueprint as I talk about in C++ had better not be regarded to be an object because of this reason.

A crucial characteristic of any object is that the object exists as a single entity: if there are 2 entities, they are not an object, but 2 objects.

A typical template is declared in a header file and the header file is included into multiple source files, which means that multiple declarations of the same template exist in multiple source files. So, when I refer to the template as a single entity, what am I talking about?

I must be talking about not a specific declaration, but about the idea (as in the Platonism sense) of the template.

Whether such an idea should be called an object is a matter of terminology choice, but in order to distinguish entities in the metaphysical world from entities in the physical world, I said that any blueprint (which is an idea) was not any object. . . . Elaborately speaking, I may use 'objective object', but I will use 'object' here after.

If one understands the concept of class in C++ correctly, he or she will already know that class is a blueprint. So, any class template is a blueprint and any class as a specialization of the template is still a blueprint. . . . I did not use the term, "instantiation" there, but used "specialization", because it does not create any object.

Likewise, inlining of any function template does not involve any instantiation, although it involves a specialization.


4: Clarification About the Terms, 'instantiation', 'explicit instantiation', and 'implicit instantiation'


Hypothesizer 7
'instantiation' means creating an object.

Well, I do not particularly claim that the term is prevalently being used that way, but I deem my usage reasonable, and I do not follow any unreasonable custom.

In fact, 'instantiation' seems to be pravalently being used meaning 'specialization' in my terminology, but such usage seems to be discrepant with the terms, 'explicit instantiation' and 'implicit instantiation', as will become clear in due course.

'explicit instantiation' means creating a function object by a specific kind of statement for that purpose.

For example, this does some explicit instantiations (note that 'ClassA.cpp' above has been renamed to 'ClassA.tpp', because it is not something to be compiled).

'theBiasPlanet/tests/templatesTest1/TemplatesInstantiator.cpp'

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/templatesTest1/ClassA.tpp"

template class ClassA <int>;

Well, is that creating an object? Did I not say that any class is not any object?

Of course, that class is not being created as an object, but the methods, 'Class <int>::ClassA ()' and 'Class <int>::methodA0 ()', of the class are being created as objects by that statement. That is the reason why I said "some explicit instantiations" (plural).

On the other hand, 'ClassB.cpp' above with "ClassA.hpp" replaced with 'ClassA.tpp' does some implicit instantiations with my GCC or Visual C++ (may do some inlining with another compiler).

In the terms, 'explicit instantiation' and 'implicit instantiation' (which are prevalent terms), I cannot help but interpret "instantiation" as meaning 'creating an object', not just 'specialization' in my terminology, because otherwise, those terms do not make sense to me.

That is because the specialization in the above implicit instantiation example is done explicitly, right? . . . In fact, what is implicit about the specialization, "ClassA <int> l_classA;"? An implicit specialization would be like something that specified the class without explicitly specifying the template parameter, or something.

What is implicit is nothing but the creation of the function object, I have to think.

So, in the prevalent terminology, 'instantiation' widely means just specialization, but used in 'implicit instantiation', it suddenly becomes to mean 'creating an object', which is what I call discrepancy: please do not change the meaning of the term wantonly.

Likewise, I cannot help but interpret 'instantiation' in 'explicit instantiation' as meaning 'creating an object'.

Well, such function objects instantiated by any implicit instantiation or explicit instantiation are weird existences.

For example, a function template can be implicitly instantiated in multiple source files, with the same set of type parameters. So, does the same function become to exist in multiple object files duplicately? . . . Yes.

Any non-static usual function cannot exist duplicately, right?

Are such implicitly instantiated function objects object-file-local, like a static function? . . . No.

Do they not cause an objects-duplication link error? . . . Weirdly, no.

Such any implicitly instantiated function should be a kind of function, but some weird special treatment seems to be given to allow it to exist duplicately. . . . I deem such special treatment quite dirty.

In fact, also explicit instantiations can cause duplication of the same function.

For example, "template class ClassA <int>;" can be written in another source file, without causing any error, although it cannot be written twice in the same source file . . . weird.

Such dirty special treatment breaks the reasonable principle that any object exists as a single entity . . .


5: The Mechanism of the Above Errors and the 2 Solutions


Hypothesizer 7
Now I understand the mechanism of the above errors.

In 'ClassB.cpp', the method calls, 'ClassA <int> l_classA' (it is a constructor call) and 'l_classA.methodA0 ()', are not (cannot be) inlined or implicitly instantiated, because the bodies of the method templates written in 'ClassA.cpp' are not shown to 'ClassB.cpp', and also, the methods are not explicitly instantiated anywhere. . . . So, the bodies of the methods are not incorporated into any object file in any way.

The prevalently recommended solution is to move the bodies of the method templates into 'ClassA.hpp', which will let 'ClassB.cpp' see the bodies enabling inlining or implicit instantiations, the compiler will decide which.

Actually, I never follow that recommendation, because 1) I am not any big fan of inlining and not even any small fan of implicit instantiation and 2) even if I opt for inlining at some occasions, I would like to preserve my source-files-structure that header files are not littered with implementation details.

I do not favor inlining (not only of derived-from-template functions but also of not-derived-from-template functions) because the source files that used the functions would have to be recompiled even when only tiny modifications to the implementation of the functions happened, end-users of the functions could see and even tweak the implementation of the functions (sometimes, some algorithms and/or data are better not revealed to end-users), etc..

I admit that for some very limited-resource run-time environments, inlining may be regrettably required, but for the typical run-time environment I tend to deal with, enhancing the hardware a little bit will be just better.

I favor implicit instantiation less, because it is weird: causing duplication of objects. . . . Certainly, I can avoid duplication by allowing implicit instantiation for each function only once (by including the 'tpp' file only into a single source file, including the 'hpp' file into the other source files). But I do not see any reason why it is better than explicit instantiation.

Anyway, whether I opt for inlining or not (implicit instantiation is not my option), I put any function template body into a 'tpp' file.

If I opt for inlining, I will disclose the 'tpp' file to let end-users of the function template to include it into their source files.

If not, I will explicitly instantiate the function template with the assumed-to-be-used sets of type parameters, into an object file, and disclose only the header file and the object file (probably in a library).

Well, a predictable objection is that preparing all such instantiations is tedious or impossible. . . . Certainly, if it is a template that is supposed to be used by open-ended people, it may be impossible, and if it is, I do not have any option but to disclose the 'tpp' file, just hoping that they will use it with discretion. If the template is supposed to be used by specific people, it will be possible. . . . As for being tedious, as any template is not guaranteed to even compile with any specific set of template parameters until the specific specialization is actually performed (in principle), such instantiations will not be things to be so shunned, as they are required anyway for test purpose.


6: How Exactly to Instantiate Any Function Template Explicitly


Hypothesizer 7
There will be no need to dig into how to inline or implicitly instantiate any function template: just include the 'tpp' file and specialize the template.

In order to explicitly instantiate the no-extra-type-parameter methods of any class template (I mean the methods that do not depend on any extra type parameter than the type parameters of the class itself), I do like this.

'theBiasPlanet/tests/templatesTest1/TemplatesInstantiator.cpp'

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/templatesTest1/ClassA.tpp"

using namespace ::theBiasPlanet::coreUtilitiesTests::templatesTest1;

template class ClassA <int>;

As I said before, the meaning of that code is not to instantiate the class, but to instantiate the no-extra-type-parameter methods.

Obviously, no extra-type-parameter method template is instantiated that way, because it cannot be, unless the extra type parameters are specified.

In order to instantiate any extra-type-parameter method of any class template, I do like these (adding to the previous code), for GCC.

@C++ Source Code
~
template double ClassA <int>::methodA1 <double> (double a_argument0);
template double ClassA <int>::methodA2 <double, float, char> (double a_argument0, float a_remainingArgument0, char a_remainingArgument1);
template ClassA <int>::ClassA <double> (double a_argument0);

Note how the variadic arguments method template, 'ClassA <T>::methodA2 <U a_argument0, V ... a_remainingArguments>' is instantiated: list the type parameters in variable length.

I said "for GCC" because that instantiation of the extra-type-parameter constructor template does not compile with Visual C++.

In order to instantiate the extra-type-parameter constructor template for Visual C++, I have to do like this (replacing the corresponding GCC-version line).

@C++ Source Code
template ClassA <int>::ClassA (double a_argument0);

It is that I cannot specify the template parameter explicitly, which seems very unreasonable.

In fact, the Visual C++ version works also for GCC.

Well, I see much unreasonableness in C++. . . . I think, we should raise complains about such unreasonableness.


7: Beware of the So-Called "Two-Stage Name Lookup"


Hypothesizer 7
There is a mechanism so-called "two-stage name lookup".

I think that it was a bad idea to adopt such a mechanism, but the mechanism stays.

Because of the mechanism, this does not compile, with an enigmatic error, "there are no arguments to ‘methodC0’ that depend on a template parameter, so a declaration of ‘methodC0’ must be available [-fpermissive]" with GCC.

'theBiasPlanet/tests/templatesTest1/ClassC.hpp'

@C++ Source Code
#ifndef __theBiasPlanet_tests_templatesTest1_ClassC_hpp__
#define __theBiasPlanet_tests_templatesTest1_ClassC_hpp__

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			template <typename T> class ClassC {
				public:
		   			 int methodC0 (int a_argument0);
				};
		}
	}
}

#endif

'theBiasPlanet/tests/templatesTest1/ClassC.tpp'

@C++ Source Code
#include "theBiasPlanet/tests/templatesTest1/ClassC.hpp"

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			template <typename T> int ClassC <T>::methodC0 (int a_argument0) {
	   		 	return a_argument0;
			}
		}
	}
}

'theBiasPlanet/tests/templatesTest1/ClassD.hpp'

@C++ Source Code
#ifndef __theBiasPlanet_tests_templatesTest1_ClassD_hpp__
#define __theBiasPlanet_tests_templatesTest1_ClassD_hpp__

#include "theBiasPlanet/tests/templatesTest1/ClassC.hpp"

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			template <typename T> class ClassD : public ClassC <T> {
				public:
					int methodD0 (int a_argument0);
			};
		}
	}
}

#endif

'theBiasPlanet/tests/templatesTest1/ClassD.tpp'

@C++ Source Code
#include "theBiasPlanet/tests/templatesTest1/ClassD.hpp"

namespace theBiasPlanet {
	namespace tests {
		namespace templatesTest1 {
			template <typename T> int ClassD <T>::methodD0 (int a_argument0) {
				return methodC0 (a_argument0);
			}
		}
	}
}

Additions to 'theBiasPlanet/tests/templatesTest1/TemplatesInstantiator.cpp'

@C++ Source Code
#include "theBiasPlanet/tests/templatesTest1/ClassD.tpp"

template class ClassD <int>;

Such simple code does not compile . . .

This Web page offers an explanation, which is also enigmatic . . .

I will understand the mechanism and solve the error in an article.


References


<The previous article in this series | The table of contents of this series | The next article in this series>