It is well known that when we want to have the declarations of templated class/functions in a header file and the their definition in a source cpp file, the must add explicit instantiation at the end of said cpp file - with the limitation that, then, our templates will only work for the explicitly defined cases.
For example, let's say we have the following header with a templated class that has an also templated member function:
// header classA.h
template<class T>
class ClassA
{
/* template<class U> */
/* friend class ClassA; */
public:
template<typename U>
void foo(U val);
};
extern template class ClassA<double>;
extern template void ClassA<double>::foo(double);
Here, extern
tells the compiler that whenever this header is included, the compiler should not try to instantiate ClassA or its member function here - the definitions will come elsewhere.
In our case, they come from the following cpp source file:
// header classA.cpp
#include <stdio.h>
#include "classA.h"
template<class T>
template<typename U>
void ClassA<T>::foo(U val)
{
// other code that uses val goes here
printf("foo\n");
}
template class ClassA<double>;
template void ClassA<double>::foo(double);
And we test the code with this main.cpp:
#include <stdio.h>
#include "classA.h"
void test(double x) { printf("test\n"); }
int main()
{
ClassA<double> a;
float x = 1.0; // this being a float throws a linker error; it must be double
a.foo(x);
test(x);
return 0;
}
The above code, as is, does not compile because x
is float
. The ld
linker (with G++ 12.3.0) throws the link error:
/usr/bin/ld: /tmp/ccVYWPa2.o: in function `main': main.cpp:(.text.startup+0x26): undefined reference to `void ClassA<double>::foo<float>(float)' collect2: error: ld returned 1 exit status bash: ./a.out: No such file or directory``
If we change x
to be a double
, then the program compiles as expected. That is, when x
is float
, the implicit conversion from float to double is not kicking in. What I don't understand is why. Surely, in general the compiler cannot unambiguously decide the type of a templated parameter. But since an explicit instantiation is being given - and just one in this simple example - I would expect the compiler to implicitly convert a float to a double when passing to foo
- because there is no other option being explicitly instantiated as to create ambiguity.
That is, I could certainly understand the ambiguity when explicitly instantiate more cases like:
template class ClassA<double>;
template void ClassA<double>::foo(double);
template class ClassA<double>;
template void ClassA<double>::foo(long double);
So, my question is two-folded:
why is the implicit conversion not happening eve if there are no ambiguous cases being explicitly instantiated?
in examples like those, is there a way to enforce implicit conversion at least when
T
=U
(i.e. perhaps based onT
, analogous to this answer to a similar but different case)?
EDIT: Obviously, I need both T
and U
separately. Otherwise I could just drop the function template and use T
instead of U
.
U
is deduced to befloat
and instantiation does not play a role at all in type deduction. Providing a explicit instantiation where the type is not a perfect match cannot prevent the compiler from generating an implicit instantiation where the type is a perfect match. It is not at all about overload resolution, so arguments about ambiguity simply do not apply here.