问题 是否有可能获得函数模板的隐式实例化的地址?


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

#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”版本的地址?我想这些类型由编译器推断。


6791
2018-04-08 18:39


起源

&hello<A<decltype(a)>> ? - Borgleader
@Borgleader:这不太对,模板参数为 hello 将是 int,但没有办法得到 int 从 a 代码原样。 - Mooing Duck
@Lalaland:参数可以是任何东西,不一定是类或类模板。 - Javier Cabezas Rodríguez
@FilipRoséen-refp:有办法搞定 返回类型,但要获取地址,您需要知道参数 后 隐式转换,在C ++中无法做到这一点。 (如果你找到一个反例,那将是开创性的,我会很高兴看到它)(相关,我在考虑 一般情况。在 这个 它的情况 威力 是可能的,因为只有一个模板函数,没有重载或转换) - Mooing Duck
@MooingDuck甚至不是C ++ 17。我们确实在库基础知识TS中获得了调用类型特征,但它们只适用于函数对象,而不是泛型过载集。 - T.C.


答案:


确定基于参数调用的函数的过程称为重载解析,以及使用它的情况下的标准列表:

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) - 调用类对象的直接初始化(8.5)的构造函数(13.3.1.3);

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

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

(2.7) - 调用转换函数以转换为glvalue或类prvalue,引用(8.5.3)将直接绑定到该值(13.3.1.6)。

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

所以,你要求的是无法做到的。不管怎样,不完全是这样。

现在,根据你想要完成的事情,有一些事情  可能:

如果你知道的话,可以获得一个指向函数的指针 精确 签名:给定 template <typename T> int hello(A<T> a, A<T> b),您可以使用以下方式获取地址: static_cast<int(*)(A<int>,A<int>)>(hello)。但是,要使其工作,您需要知道返回类型(您可以使用它获得 decltype),您需要知道参数类型(可能与参数类型不同,并且您无法可靠地获取)。

也可以获得一个指向函数的指针,该函数在被调用时将具有与之相同的效果 hello

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

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

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

这是你能得到的最好的。


8
2018-04-08 18:55



可能想解释一下 +。这有点模糊。 - T.C.
@ T.C。好点,补充说明。
如果复制,指向lambda的指针与hello的效果不同 A<int> 有任何副作用。如果你移动,那么如果移动ctor有副作用(或如果 hello 不同地对待rvalues)它不会有相同的效果。如果 hello 通过引用,对对象所做的任何更改都不会在lambda解决方案中传播出去。所有这些基本上都需要检查功能模板的实现 hello 并且它的签名达到了你可以手动完成的程度,不是吗? - Yakk - Adam Nevraumont
@Yakk你知道怎么打电话 hello,因此您可以更新lambda以匹配,而无需了解实现细节 hello。如果你知道你想传递左值,那就写吧 auto callable = +[](A<int> &a, A<int> &b) { return hello(a, b); };。即使这样也会奏效 hello 取值。同样rvalues,除了添加 std::move:即使这样也会奏效 hello 取值。但是你确实提出了一个有效点:在某些情况下,参数的复制或移动具有可见效果,并且无法避免。


我试过这个 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;
}

它似乎输出了一个内存地址。


2
2018-04-08 19:51



我在一篇删除的帖子中通过提问者的评论澄清了这个问题:“我希望这些类型能够被编译器推断出来。” - Mooing Duck
@MooingDuck上面的编辑更好吗? - quamrana
是。这适用于参数类型完全匹配的OP情况。 - Mooing Duck


@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

当然,这需要声明一个知道的通用代理 名称 目标函数(没有别的),这可能不被接受为@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);

1
2018-04-09 12:45



欢迎来到SO。获得足够的声誉评论只需一个好的答案,我鼓励你这样做,如果这是你想要留下的评论水平,这是有用的。 :) Lambdas可以在C ++ 14中进行模板化(查找 auto 参数),但这没有用,模板化的lambda可以转换为多个函数指针类型,所以使用的技巧 + 强制单一可能的转换不起作用。但...
...你不需要模板化的lambdas,并且你不需要lambdas与被调用的函数完全匹配,你只需要匹配你想传递的参数。 lambda主体可以处理从参数类型到包装函数的参数类型的任何隐式转换。例如, void f(long l) { } int main() { auto x = +[](int a) { f(a); }; } 很好,给 x 类型 void(*)(int)。它可以与它的类型完全匹配 &f。
是的,我正在考虑避免声明具有特定数量参数的lambda。请参阅我的回复编辑。 - Lluís Vilanova