【问题标题】:Is it possible to obtain the address of a implicit instantiation of a function template?是否可以获得函数模板的隐式实例化的地址?
【发布时间】:2026-01-15 04:45:02
【问题描述】:

我想知道是否有任何方法可以获取由一组特定参数生成的函数模板的实例化地址。

#include <iostream>

template <typename T>
class A {}; 

template <typename T, typename T2>
int hello(A<T> a, A<T2> b, int c)
{
    return 69; 
}

int main()
{
    A<int> a;
    A<float> b;
    std::cout << (&hello)(a, b, 3) << "\n";                                                                                                                                                           

    return 0;
}

此代码打印函数调用返回的值。如何打印为 a 和 b 参数实例化的“hello”版本的地址?我希望编译器能够推断出类型。

【问题讨论】:

  • &amp;hello&lt;A&lt;decltype(a)&gt;&gt; ?
  • @Borgleader:这不太对,hello 的模板参数将是int,但无法使用代码原样从a 获取int
  • @Lalaland:参数可以是任何东西,不一定是类或类模板。
  • @FilipRoséen-refp:有办法获取返回类型,但是要获取地址,需要知道参数after隐式转换,在 C++ 中没有办法做到这一点。 (如果你找到一个反例,那将是开创性的,我会很高兴看到它)(相关,我正在考虑the general case。在 this 的情况下它可能 i> 可能,因为只有一个模板函数,没有重载或转换)
  • @MooingDuck 甚至没有 C++17。我们确实在库基础 TS 中获得了调用类型特征,但它们仅适用于函数对象,而不适用于泛型重载集。

标签: c++ templates c++11


【解决方案1】:

根据参数确定要调用的函数的过程称为重载解析,标准列出了在哪些情况下使用它:

13.3 重载分辨率 [over.match]

2 重载解析选择要在语言内的七个不同上下文中调用的函数:

(2.1) -- 调用以函数调用语法命名的函数(13.3.1.1.1);

(2.2) -- 在类对象上调用函数调用运算符、指针到函数的转换函数、引用到指针到函数的转换函数或引用到函数的转换函数命名为 在函数调用语法中(13.3.1.1.2);

(2.3) -- 调用表达式中引用的运算符 (13.3.1.2);

(2.4) -- 为类对象 (13.3.1.3) 的直接初始化 (8.5) 调用构造函数;

(2.5) -- 为类对象 (13.3.1.4) 的复制初始化 (8.5) 调用用户定义的转换;

(2.6) -- 调用转换函数以从类类型的表达式中初始化非类类型的对象 (13.3.1.5);和

(2.7) -- 调用转换函数以转换为引用 (8.5.3) 将直接绑定到的泛左值或类纯右值 (13.3.1.6)。

其中,唯一适用于常规函数的是 2.1,它需要一个 f(args) 上下文,它只告诉调用者结果。

因此,您所要求的无法完成。无论如何,不​​完全是。

现在,根据您想要完成的任务,有些事情可能的:

如果你知道 exact 签名,则可以获得指向函数的指针:给定template &lt;typename T&gt; int hello(A&lt;T&gt; a, A&lt;T&gt; b),你可以使用它来获取地址:static_cast&lt;int(*)(A&lt;int&gt;,A&lt;int&gt;)&gt;(hello)。但是,要使其正常工作,您需要知道返回类型(您可以使用 decltype 获得),并且您需要知道参数类型(可能与参数类型不同,并且您是'不能可靠地获得)。

还可以获得一个指向函数的指针,该函数在调用时会产生与hello 相同的效果:

auto callable = +[](A<int> a, A<int> b) { return hello(a, b); };

[](A&lt;int&gt; a, A&lt;int&gt; b) { return hello(a, b); } 创建一个没有任何捕获的 lambda,并且没有任何捕获的 lambda 可以隐式转换为匹配类型的函数指针。 + 强制使用该转换,而不需要拼写出类型。

但是,这与hello 的地址不同,因此可能不适合后续比较。

这是你能得到的最好的。

【讨论】:

  • 可能想解释一下+。有点晦涩。
  • 如果复制A&lt;int&gt; 有任何副作用,则指向 lambda 的指针与 hello 的效果不同。如果您移动,那么如果移动 ctor 有副作用(或者如果 hello 以不同方式处理右值),则不会产生相同的效果。如果hello 采用引用,则对对象所做的任何更改都不会在 lambda 解决方案中传播出去。所有这些基本上都需要检查函数模板hello 的实现及其签名到您最好手动完成的级别,不是吗?
  • @Yakk 你知道你想怎么调用hello,所以你可以更新lambda来匹配,而不必知道hello的实现细节。如果您知道要传递左值,请写auto callable = +[](A&lt;int&gt; &amp;a, A&lt;int&gt; &amp;b) { return hello(a, b); };。即使hello 按值取值,它也会起作用。类似的右值,除了添加的std::move:即使hello 按值取值,它也可以工作。但是您确实提出了一个有效的观点:在某些情况下,参数的复制或移动会产生明显的效果,并且这是无法避免的。
【解决方案2】:

我在Ideone中试过这个:

// A and hello defined as OP
int main()
{
    A<int> a;
    A<float> b;

    decltype(hello(a,b,3)) (*pf)(decltype(a), decltype(b), decltype(3))=hello;
    std::cout << (void*)pf << "\n";                                                                                                                                                           

    return 0;
}

好像输出了一个内存地址。

【讨论】:

  • 我通过提问者对已删除帖子的评论澄清了这个问题:“我希望编译器推断出类型。”
  • 是的。这适用于参数类型完全匹配的 OP 情况。
【解决方案3】:

@hvd:AFAIK,你不能在不知道你想要包装的函数的签名的情况下声明一个 lambda(lambdas 不能被模板化)。但是您可以使用带有静态方法的中间类:

#include <functional>
#include <iostream>

template<class A>
void func(A a, int b)
{
    std::cout << "a=" << a << " b=" << b << "\n";
}

template<class... A>
class proxy_func
{
public:
    static auto call(A... args) -> decltype(func(args...))
        {
            return func(args...);
        }
};

template<template<class...> class P, class... User>
void* addr(User... user)
{
    return (void*)&P<User...>::call;
}

template<template<class...> class P, class... User>
auto call(User... user) -> decltype(P<User...>::call(user...))
{
    return P<User...>::call(user...);
}

template<class T>
void test()
{
    T value = 1;
    printf("func > %p\n", &func<T>);
    printf("func > ");
    func(value, 1);
    printf("proxy> %p\n", &proxy_func<T, int>::call);
    printf("proxy> ");
    proxy_func<T, int>::call(value, 1);
    printf("auto > %p\n", addr<proxy_func>(value, 1));
    printf("auto > ");
    call<proxy_func>(value, 1);
}

int main(int argc, char **argv)
{
    printf("==int==\n");
    test<int>();
    printf("==long==\n");
    test<long>();
}

结果是这样的:

g++ -std=c++11 -o /tmp/test /tmp/test.cpp && /tmp/test
==int==
func > 0x400a8d
func > a=1 b=1
proxy> 0x400ae6
proxy> a=1 b=1
auto > 0x400ae6
auto > a=1 b=1
==long==
func > 0x400b35
func > a=1 b=1
proxy> 0x400b91
proxy> a=1 b=1
auto > 0x400b91
auto > a=1 b=1

当然,这需要声明一个知道目标函数的 name 的通用代理(仅此而已),这可能无法作为@JavierCabezasRodríguez 的解决方案接受。

PS:抱歉,我没有将它作为评论发布,但我没有足够的声誉。

编辑

使用代理类而不是 lambda 无需知道参数的数量,因此您可以使用 hackishly 宏方法来包装任何目标函数:

#define proxy(f)                                \
    template<class... A>                        \
    class proxy_ ## f                           \
    {                                           \
    public:                                                     \
        static auto call(A... args) -> decltype(f(args...))     \
        {                                                       \
            return f(args...);                                  \
        }                                                       \
    }

proxy(func);

【讨论】:

  • 欢迎来到 SO。只需一个好的答案即可获得足够的评论声誉,如果这是您想要离开的 cmets 级别,我鼓励您这样做,这很有用。 :) Lambda 可以在 C++14 中进行模板化(查找 auto 参数),但这无济于事,模板化的 lambda 可以转换为多种函数指针类型,因此使用 + 强制单一可能的转换不起作用。但是...
  • ...你不需要模板化的 lambdas,也不需要 lambdas 与被调用的函数完全匹配,你只需要匹配你想要传递的参数。 lambda 主体可以处理从参数类型到包装函数的参数类型的任何隐式转换。例如,void f(long l) { } int main() { auto x = +[](int a) { f(a); }; } 很好,并给出x 类型void(*)(int)。与&amp;f的类型不完全匹配也没关系。
  • 对,我正在考虑避免声明具有特定数量参数的 lambda。请参阅我的回复中的编辑。
最近更新 更多