防止函数模板实例化的机制

huangapple go评论71阅读模式
英文:

Mechanism to prevent instantiations of a function template

问题

我想声明一个根据typename来声明函数,这样我就可以通过类型引用它。关键是,我希望即使是无名称的lambda类型,也可以这样做。当然,经典的方法是使用模板:

template <typename T>
void template_foo() {}

现在我可以通过函数的类型来调用这个函数:

auto my_lambda = [] {};
template_foo<decltype(my_lambda)>();

编译器会为每个作为模板参数传递的类型实例化一个template_foo符号。

然而,我想构建一个类似于反射的工具(基于LLVM),它将为程序中使用的所有T定义template_foo<T>。由于decltype(my_lambda)不能命名,它无法生成声明外部模板实例化的头文件,而且从原则上讲,这也不应该需要 - 编译器应该能够在不实例化的情况下引用mangled符号。我如何防止编译器实例化所有T的这个函数,以便我可以在不同的TU中实例化它?

请注意,模板变量可以做类似的事情:

template <typename T>
struct Foo {
    static int my_int;
}

当使用Foo<decltype(my_lambda)>::my_int时,编译器被迫假设它在其他的翻译单元中定义(Clang的最新版本会警告这一点,但据我所知,这个警告是因为这很少是用户想要的,而不是因为它是非法的)。

然而,没有等效的机制来指定一个特殊化函数。

下面是一个完整的MVCE示例,演示了我想要做的事情:

#include <cstdint>
#include <cstdio>

template <typename T>
struct Foo {
    static int my_int;
};

template <typename T>
int my_func();

int main() {
    // 没有错误,但由于下面的错误而无法工作
    printf("%i", my_func<int>());

    // 工作 - 生成了代码,但没有定义则无法链接
    printf("%i", Foo<int>::my_int); 
}

// 错误:实例化后的‘int my_func() [with T = int]’的特化
template <>
int my_func<int>() {
    return 42;
}

我如何防止编译器实例化任何版本的模板?

英文:

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 &lt;typename T&gt;
void template_foo() {}

Now I can call this function by using the type of a function:

auto my_lambda = [] {};
template_foo&lt;decltype(my_lambda)&gt;();

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&lt;T&gt; 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 &lt;typename T&gt;
struct Foo {
    static int my_int;
}

When Foo&lt;decltype(my_lambda)&gt;::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 &lt;cstdint&gt;
#include &lt;cstdio&gt;

template &lt;typename T&gt;
struct Foo {
    static int my_int;
};

template &lt;typename T&gt;
int my_func();

int main() {
    // no error here, but can&#39;t work due to below error
    printf(&quot;%i&quot;, my_func&lt;int&gt;());

    // works - code emitted, but does not link without a definition
    printf(&quot;%i&quot;, Foo&lt;int&gt;::my_int); 
}

// ERROR: specialization of &#39;int my_func() [with T = int]&#39; after instantiation
template &lt;&gt;
int my_func&lt;int&gt;() {
    return 42;
}

How can I prevent the compiler from instantiating any versions of a template?

答案1

得分: 4

Option A: 使用 friend 非模板函数
模板有一个限制,即在实例化后无法专门化它们。但是,通过 friend,我们可以在类模板内生成非模板函数并调用它们:

#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() {
    // 打印 42
    printf("%i\n", my_func<int>());
}

// 可以在任何地方定义,包括在不同的TU中
int call(tag<int>) {
    return 42;
}

这利用了 int call(tag<T>) 不是函数模板,而是一个接受 tag<T> 作为参数的具体函数,例如 tag<int>

通过对 tag 类的部分专门化,我们还可以决定为某些 T 定义 call

// 所有浮点类型的部分专门化
template<typename T>
requires std::floating_point<T>
struct tag<T> {
    // 隐藏的 friend
    friend int call(tag<T>) {
        return 123;
    }
};

Option B: 使用第二个模板函数
如果我们不需要部分专门化,我们也可以使用另一个函数模板来解决这个问题:

// 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>();

编译器将发出 call my_func<int>()call my_func<float>()。我们处理了第二个文件中的一些专门化情况,其中 my_func 没有专门化,只有 my_func_impl 专门化。

Lambda 表达式
至于 lambda 表达式:显然,使用头文件和源文件之间的链接调用 call(tag<decltype(some_lambda)>) 会遇到问题。如果您想在头文件中调用此函数并在源文件中定义它,您需要引用完全相同的 lambda 类型,这意味着您需要一个类型别名:

// header:
using my_lambda = decltype([] {});
...
my_func<my_lambda>();

// source:
int call(tag<my_lambda>) {
   ...
}
英文:

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 &lt;cstdint&gt;
#include &lt;cstdio&gt;

template&lt;typename T&gt;
struct tag {
    friend int call(tag&lt;T&gt;);
};

template&lt;typename T&gt;
int my_func() {
    return call(tag&lt;T&gt;{});
}

int main() {
    // prints 42
    printf(&quot;%i\n&quot;, my_func&lt;int&gt;());
}

// Can be defined anywhere, including in a different TU
int call(tag&lt;int&gt;) {
    return 42;
}

This exploits the fact that int call(tag&lt;T&gt;) is not a function template, but a concrete function that takes a tag&lt;T&gt; as an argument, such as tag&lt;int&gt;.

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&lt;typename T&gt;
requires std::floating_point&lt;T&gt;
struct tag&lt;T&gt; {
    // hidden friend
    friend int call(tag&lt;T&gt;) {
        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 &lt;cstdint&gt;
#include &lt;cstdio&gt;

template&lt;typename T&gt;
int my_func();

int main() {
    printf(&quot;%i\n&quot;, my_func&lt;int&gt;());
    printf(&quot;%i\n&quot;, my_func&lt;float&gt;());
}
// extra.cpp
template&lt;typename T&gt;
int my_func_impl();

template&lt;typename T&gt;
int my_func() {
    return my_func_impl&lt;T&gt;();
}

template&lt;typename T&gt;
int my_func_impl() {
    return 0;
}

template&lt;&gt;
int my_func_impl&lt;int&gt;() {
    return 42;
}

template int my_func&lt;int&gt;();
template int my_func&lt;float&gt;();

The compiler will emit call my_func&lt;int&gt;() and call my_func&lt;float&gt;(). 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&lt;decltype(some_lambda)&gt;) 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&lt;my_lambda&gt;();

// source:
int call(tag&lt;my_lambda&gt;) {
   ...
}

huangapple
  • 本文由 发表于 2023年6月9日 01:36:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76434375.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定