Возможно ли получить адрес неявного экземпляра шаблона функции?
Я хотел бы знать, есть ли способ получить адрес экземпляра шаблона функции, созданного определенным набором аргументов.
#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? Я бы хотел, чтобы типы были выведены компилятором.
Ответы
Ответ 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) - вызов конструктора для прямой инициализации (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
), и вам нужно знать типы параметров (которые могут отличаться от типов аргументов и которые вы aren надежно получить).
Также можно получить указатель на функцию, которая при вызове будет иметь тот же эффект, что и hello
:
auto callable = +[](A<int> a, A<int> b) { return hello(a, b); };
[](A<int> a, A<int> b) { return hello(a, b); }
создает лямбда без каких-либо захватов, а lambdas без каких-либо захватов может быть неявно преобразован в указатель функции соответствующего типа. +
заставляет использовать это преобразование, не требуя, чтобы тип был прописан.
Однако это не будет иметь тот же адрес, что и 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;
}
Казалось, что он выдает адрес памяти.
Ответ 3
@hvd: AFAIK, вы не можете объявить лямбда, не зная подписи функции, которую вы хотите обернуть (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: Извините, я не публиковал это как комментарий, но у меня недостаточно репутации.
Edit
Использование прокси-класса вместо лямбда не требует знать количество параметров, так что вы можете использовать хакерский макро-подход для обертывания любой целевой функции:
#define proxy(f) \
template<class... A> \
class proxy_ ## f \
{ \
public: \
static auto call(A... args) -> decltype(f(args...)) \
{ \
return f(args...); \
} \
}
proxy(func);