Возможно ли получить адрес неявного экземпляра шаблона функции?

Я хотел бы знать, есть ли способ получить адрес экземпляра шаблона функции, созданного определенным набором аргументов.

#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);