Как передать произвольную функцию-член ЛЮБОГО класса в шаблон, чтобы разрешить его подпись?

В моем движке у меня есть простая система отражения, заполняемая информацией о классах во время компиляции (то есть построенная на наборе шаблонов, что позволяет мне автоматизировать процесс генерации метаинформации).

Рассмотрим следующий пример:

class Type
{
  //...
  Map<PropertyHash, TypeProperty> _properties;
};

Для каждого типа есть функция:

template <class T>
void InitializeType(TypeInitializer* typeInitializer);

отвечает за инициализацию типа. TypeInitializer имеет несколько методов, используемых для добавления полей и базовых типов к типу. Таким образом, каждый новый тип требует только специализации этой функции. Позже, когда тип запрашивается в первый раз, TypeDatabase создает конкретный объект Type и вызывает для него InitializeType() (TypeInitializer получает указатель на тип во время построения). Например:

struct CST
{
    const float* fptr;
    volatile Uint32 vuint;
    void** vptr;
};

template <>
SvrInline void InitializeType<CST>(TypeInitializer* typeInitializer)
{
    typeInitializer->AddProperty("fptr", &CST::fptr);
    typeInitializer->AddProperty("vuint", &CST::vuint);
    typeInitializer->AddProperty("vptr", &CST::vptr);
}

Вот и все. Вся магия делается в конструкторе TypeProperty, который объявлен как:

template <class Object_type, class Property_type>
TypeProperty(const char* fieldName, Property_type (Object_type::* propertyPtr));

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

Теперь мне нужно что-то идентичное для функции членов. «Идентичный» означает, что я могу добавить функцию точно так же, как сейчас добавляю свойства.

Моей первой мыслью были вариативные шаблоны (мой движок построен с учетом полной поддержки функций С++ 11):

template <typename Object_t, typename Return_t, typename... Args>
TypeMethod(const char* methodName, Return_t (Object_t::*)(Args&&... args)
{
  //What now?
}

Однако я не знаю, как мне извлекать типы из args. Я видел статью с подходом, использующим перегрузку функций:

template <typename P, typename R, typename Arg1, typename... Args>
void Func(R (P::*)(Arg1 arg1, Args&&... args))
{
}

template <typename P, typename R, typename... Args>
void Func(R (P::*)(Args&&... args))
{
}

template <typename P, typename R>
void Func(R (P::*)())
{
}

Функция была "переадресована" рекурсивно (я знаю, что это не настоящая рекурсия) с каждым разом на один параметр меньше. Однако я не понимаю, как это подходит для моего случая.


person Mateusz Grzejek    schedule 11.02.2014    source источник
comment
Найдено в accept-any-kind- of-callable-and-also-know-argument-type, coliru.stacked- crooked.com/a/00750bf7564ab6d4 тоже может помочь.   -  person Jarod42    schedule 11.02.2014
comment
Спасибо за этот чрезвычайно полезный ресурс!   -  person Mateusz Grzejek    schedule 11.02.2014


Ответы (3)


Нет необходимости в рекурсии, просто используйте пакетное расширение:

template <typename Object_t, typename Return_t, typename... Args>
TypeMethod(const char* methodName, Return_t (Object_t::*)(Args&&... args)
{
  setName(methodName);
  setObjectType<Object_t>();
  setReturnType<Return_t>();
  auto dummy[] = {0, (addArgumentType<Args>(), 0)...};
}

Мы помещаем расширение пакета в скобочный список инициализации, чтобы гарантировать, что вызовы addArgumentType<...> выполняются в правильном порядке.

person ecatmur    schedule 11.02.2014
comment
Вы должны добавить хотя бы 1 элемент в dummy, когда нет аргументов. - person Jarod42; 11.02.2014
comment
Мне нравится это решение. Однако я не могу понять, почему вы это сделали: (addArgumentType‹Args›(), 0). Что означает этот «0»? И какой тип элементов в dummy[]? Мне также любопытно, будет ли это работать в случае передачи функции без параметров. - person Mateusz Grzejek; 11.02.2014
comment
@MateuszGrzejek: (addArgumentType<Args>(), 0)... используйте оператор запятой для вызова addArgumentType<Args>() и вычислите 0 для расширения. вам просто нужно иметь {0, (addArgumentType<Args>(), 0)...} для поддержки 0 arg. auto это int. - person Jarod42; 11.02.2014
comment
Я невнимательно это читал :) Эту загадку уже разгадал. Я изменил этот подход для хранения информации о проанализированных аргументах. Дополнительный «маркер» не требуется, если предоставляется дополнительная специализация ctor. Работает как шарм. Хотя все ответы были очень полезными, этот идеально подходит для моего стиля программирования. - person Mateusz Grzejek; 11.02.2014
comment
Мне также любопытно, как поведет себя это решение, если будет передана функция с параметром vararg (например, void f(int,...)). - person Mateusz Grzejek; 11.02.2014
comment
@MateuszGrzejek вы должны предоставить перегрузки с двойным многоточием (......). См. stackoverflow.com/questions/5625600/ - person ecatmur; 11.02.2014

...

template <typename P, typename R, typename Arg1, typename Arg2>
void Func(R (P::*)(Arg1 arg1, Arg2 arg2))
{
    // function type is R (P::*)(Arg1 arg1, Arg2 arg2)
}

template <typename P, typename R, typename Arg1>
void Func(R (P::*)(Arg1 arg1))
{
    // function type is R (P::*)(Arg1 arg1)
}

template <typename P, typename R>
void Func(R (P::*)())
{
     // function type is R (P::*)()
}

Я не знаком с вариативными аргументами. Это было единственным решением до C++11. Но теперь новые возможности C++11 могут решить эту проблему более элегантно.

КСТАТИ. Впервые я увидел этот способ разрешения подписи в реализации библиотеки boost.pyton.

person ArmanHunanyan    schedule 11.02.2014

Используя decompose_mem_fun_ptr из http://coliru.stacked-crooked.com/a/00750bf7564ab6d4

template <typename M>
TypeMethod(const char* methodName, M&m)
{
    setName(methodName);
    setObjectType<typename decompose_mem_fun_ptr<M>::class_type>();
    setReturnType<typename decompose_mem_fun_ptr<M>::return_type>();

    // use other info from decompose_mem_fun_ptr<M>.

    using args_type = typename decompose_mem_fun_ptr<M>::arguments; 

    internal_setArgs<args_type>(make_index_sequence<std::tuple_size<args_type>::value>());
}

template<typename Tuple, std::size_t...Is>
void internal_setArgs(index_sequence<Is...>)
{
    // Assuming setArg<T>(i_th).
    int dummy[] = {0, (setArg<typename std::tuple_element<Is, Tuple>::type>(Is), 0)...};
    static_cast<void>(dummy); // silent warning about unused variable.
}

для index_sequence и make_index_sequence:

#if 1 // Not in C++11
#include <cstdint>

template <std::size_t ...> struct index_sequence {};

template <std::size_t N, std::size_t ...Is>
struct make_index_sequence : make_index_sequence < N - 1, N - 1, Is... > {};

template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};

#endif // make_index_sequence
person Jarod42    schedule 11.02.2014