Есть ли способ разбить шаблонный указатель на функцию?
В настоящее время у меня есть шаблон так:
template<typename func, typename ret, typename... args> class Entry{
public:
PVOID Address;
ret operator()(args...){
return ((func) this->Address)(args...);
}
};
И я использую его так:
Entry<int(*)(int), int, int> func;
// ^func ^ret ^args
func.Address = (PVOID) 0xDEADC0DE;
func(123); // calls 0xDEADC0DE with '123' as argument
Однако мне было интересно, возможно ли иметь только это:
Entry<int(*)(int)> func;
// ^only specifying the function's prototype once instead of breaking it down
func(123);
Если бы он был таким, я не смог бы перегружать operator()
, поскольку я не могу разделить тип указателя функции на аргументы и возвращаемый тип (так что я могу написать return_type operator()(args...)
).
Я использую VS2013 Nov 2013 CTP
3 ответа:
Вы можете сделать это с помощью такой специализации:
// Entry has one template argument template<typename func> class Entry; // and if it's a function type, this specialization is used as best fit. template<typename ret, typename... args> class Entry<ret(args...)>{ public: PVOID Address; ret operator()(args... a){ return ((ret(*)(args...)) this->Address)(a...); } }; int main() { Entry<int(int)> foo; foo.Address = (PVOID) 0xdeadc0de; func(123); }
Чтобы использовать его с типом указателя функции, как в вашем примере (хотя мне больше нравится синтаксис типа функции), напишите
// here ------v template<typename ret, typename... args> class Entry<ret(*)(args...)>{
Добавление: еще одна вещь пришла ко мне, когда я был на обеде: есть (небольшая) проблема с
operator()
, которая может или не может касаться вас: вы не сталкиваетесь с проблемами пересылки параметров, которые передаются по значению или по ссылке lvalue, потому что они просто передаются, как они были передается (поскольку список аргументов точно такой же для указателя функции иoperator()
), но если вы планируете использовать параметры rvalue-reference, это не работает для них неявно. По этой причинеEntry<int(int&&)> foo; foo(123);
Не компилируется. Если вы планируете использовать это с функциями, которые принимают ссылки rvalue,
operator()
можно исправить следующим образом:ret operator()(args... a){ // explicit forwarding ----v return ((ret(*)(args...)) this->Address)(std::forward<args>(a)...); }
Возможна частичная специализация, как показано в посте @ Wintermutes.
Однако то, что вы пытаетесь, должно быть возможно и без него:template <typename func> class Entry{ public: PVOID Address; template <typename... Args> auto operator()(Args&&... args) -> decltype( ((func*) Address)(std::forward<Args>(args)...) ) { return ((func*) Address)(std::forward<Args>(args)...); } };
Аргумент шаблона должен быть типом функции. Однако вы можете заставить его работать как с функцией, так и с указателем на типы функций с небольшим изменением в выражении возврата: вместо использования
func*
в качестве целевого типа приведения используйтеtypename std::remove_pointer<func>::type*
, т. е.template <typename... Args> auto operator()(Args&&... args) -> decltype( ((typename std::remove_pointer<func>::type*) Address)(std::forward<Args>(args)...) ) { return ((typename std::remove_pointer<func>::type*) Address)(std::forward<Args>(args)...); }
Демо-версия.
Подход метапрограммирования. Во-первых, некоторые признаки указателя, которые пытаются сохранить информацию о соглашении вызова:
template<class...>types {using type=types;}; enum class calling_convention { cdecl, clrcall, stdcall, fastcall, thiscall, vectorcall, }; template<class Sig> struct signature_properties; template<class R, class...Args> struct signature_properties { using return_type = R; using argument_types = types<Args...>; }; template<class FuncPtr> struct function_properties; #define CAT_(A,B) A##B #define CAT(A,B) CAT_(A,B) #define CALLING_CONVENTION_SUPPORT( CONVENTION ) \ template<class R, class... Args> \ struct function_properties< R(CAT(__, CONVENTION) *)(Args...) >: \ signature_properties<R(Args...)> \ { \ using type = R(CAT(__, CONVENTION) *)(Args...) \ static const calling_convention convention = calling_convention::CONVENTION; \ static type from_pvoid(void const* pvoid) { \ return static_cast<type>(pvoid); \ } \ } CALLING_CONVENTION_SUPPORT(cdecl); CALLING_CONVENTION_SUPPORT(clrcall); CALLING_CONVENTION_SUPPORT(stdcall); CALLING_CONVENTION_SUPPORT(fastcall); CALLING_CONVENTION_SUPPORT(thiscall); CALLING_CONVENTION_SUPPORT(vectorcall); #undef CAT #undef CAT_ #undef CALLING_CONVENTION_SUPPORT
Мерзкие макросы. И серьезный перебор. И непроверенные. Но вы поняли идею.
Далее, помощник для выполнения работы:
template<class FuncPtrType, class R, class Args> struct helper; template<class FuncPtrType, class R, class... Args> struct helper<FuncPtrType, R, types<Args...>> { FuncPtrType ptr; R operator()(Args...args)const { return ptr(std::forward<Args>(args)...); } helper(FuncPtrType p):ptr(p) {}; helper( helper const& )=default; helper& operator=( helper const& )=default; };
Совершенная переадресация в помощнике также была бы заманчивой.
Наконец, мы используем класс признаков выше, чтобы перевести работу из
Entry
вhelper
:template<class FuncPtrType> struct Entry:helper< FuncPtrType, typename signature_properties<FuncPtrType>::return_type, typename signature_properties<FuncPtrTpye>::arguments > { using parent = helper< FuncPtrType, typename signature_properties<FuncPtrType>::return_type, typename signature_properties<FuncPtrTpye>::arguments >; Entry(void const* pvoid):parent( static_cast<FuncPtrType>(pvoid) ) {} };
За исключением того, что мы включаем конструктор в
Одно изменение состоит в том, что мы приводим отEntry
, чтобы взятьvoid const*
и перенаправьте типизированный указатель наhelper
.void*
к нашему типу функции в самой ранней точке, которую мы знаем, что это тип функции, а не в точке, которую мы вызываем.