4

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?

Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51
George Hilliard
  • 15,402
  • 9
  • 58
  • 96
  • 1
    Can you compile the file which implicitly instantiates the template with `-fno-implicit-templates`? Then explicitly instantiate in another file. Not 100% sure if this is workable for you. – John Zwinck Jun 08 '23 at 17:43
  • 1
    `template<> int my_func()` should be declared before it is used. (Here, it would use primary template, specialization is unknown) – Jarod42 Jun 08 '23 at 17:44
  • @Jarod42 how will I do that for a lambda type which may only exist in a function body? – George Hilliard Jun 08 '23 at 17:47
  • @JohnZwinck that is an interesting option but I think it would conflict with other template usage which would not have the external definitions magically provided (== explicitly instantiated). – George Hilliard Jun 08 '23 at 17:48

1 Answers1

4

Option A: friend non-template functions

Templates have the limitation that you cannot specialize them after they were instantiated. With friends 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;
    }
};

Option B: use second template function

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.

Lambda Expressions

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>) {
   ...
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • This is very clever! I slightly prefer option A because it doesn't create the intervening `my_func` -> `my_func_impl` (although this will be inlined by any decent compiler) and because if the function accepts `T` then there is no need for the `tag` to do overload resolution. In both cases the main technique is to forward away from the generic template altogether, which is perfect. I am not so worried about binding the lambda expressions' types, because I expect my code generation tool to be able to understand the mangled name of the lambdas, and be able to emit the corresponding symbol. – George Hilliard Jun 09 '23 at 05:04