I would like to declare a function per typename
, so that I can refer to it by type. Critically, I would like to do this even for nameless lambda types. The classic way to do this is of course a template:
template <typename T>
void template_foo() {}
Now I can call this function by using the type of a function:
auto my_lambda = [] {};
template_foo<decltype(my_lambda)>();
and the compiler instantiates a template_foo
symbol for each type passed as a template parameter.
However, I want to build a reflection-like tool (based on LLVM) that will define template_foo<T>
for all T
that is used in the program. Since decltype(my_lambda)
can't be named, it can't generate a header that declares an extern template instantiation, and anyway in principle that should not be required -- the compiler ought to be able to refer to the mangled symbol without instantiating it. How can I prevent the compiler from instantiating this function for all T
so I can instantiate it myself in a different TU?
Note that a related thing is possible with template variables:
template <typename T>
struct Foo {
static int my_int;
}
When Foo<decltype(my_lambda)>::my_int
is used, the compiler is forced to assume it is defined in some other translation unit (recent versions of Clang warn about this but as far as I can tell, the warning is because this is seldom what users want, not because it is illegal).
However, there is no equivalent mechanism for specifying a specialized function.
Here's a complete MVCE that demonstrates what I want to do:
#include <cstdint>
#include <cstdio>
template <typename T>
struct Foo {
static int my_int;
};
template <typename T>
int my_func();
int main() {
// no error here, but can't work due to below error
printf("%i", my_func<int>());
// works - code emitted, but does not link without a definition
printf("%i", Foo<int>::my_int);
}
// ERROR: specialization of 'int my_func() [with T = int]' after instantiation
template <>
int my_func<int>() {
return 42;
}
How can I prevent the compiler from instantiating any versions of a template?
friend
non-template functionsTemplates have the limitation that you cannot specialize them after they were instantiated. With friend
s however, we can generate non-template functions within a class template and call those:
#include <cstdint>
#include <cstdio>
template<typename T>
struct tag {
friend int call(tag<T>);
};
template<typename T>
int my_func() {
return call(tag<T>{});
}
int main() {
// prints 42
printf("%i\n", my_func<int>());
}
// Can be defined anywhere, including in a different TU
int call(tag<int>) {
return 42;
}
This exploits the fact that int call(tag<T>)
is not a function template, but a concrete function that takes a tag<T>
as an argument, such as tag<int>
.
With partial specializations of the tag
class, we can also decide to define call
for some T
:
// partial specialization for all floating point types
template<typename T>
requires std::floating_point<T>
struct tag<T> {
// hidden friend
friend int call(tag<T>) {
return 123;
}
};
If we don't need to partially specialize, we could just as well use another function template to solve this issues:
// main.cpp
#include <cstdint>
#include <cstdio>
template<typename T>
int my_func();
int main() {
printf("%i\n", my_func<int>());
printf("%i\n", my_func<float>());
}
// extra.cpp
template<typename T>
int my_func_impl();
template<typename T>
int my_func() {
return my_func_impl<T>();
}
template<typename T>
int my_func_impl() {
return 0;
}
template<>
int my_func_impl<int>() {
return 42;
}
template int my_func<int>();
template int my_func<float>();
The compiler will emit call my_func<int>()
and call my_func<float>()
. We deal with the fact that we have some specializations in the second file, where my_func
isn't specialized, only my_func_impl
is.
As for lambda expressions: you obviously run into a problem with linking a call call(tag<decltype(some_lambda)>)
between header and source file. If you want to call this function in the header and define it in the source, you will need to refer to the exact same lambda type, meaning you need a type alias for it:
// header:
using my_lambda = decltype([] {});
...
my_func<my_lambda>();
// source:
int call(tag<my_lambda>) {
...
}