Есть ли способ разбить шаблонный указатель на функцию?


В настоящее время у меня есть шаблон так:

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 6

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* к нашему типу функции в самой ранней точке, которую мы знаем, что это тип функции, а не в точке, которую мы вызываем.