Предположим, у меня есть некоторые метаданные отражения, которые имеют следующую информацию:
enum class type { t_int, t_double, t_str /* etc... */ };
struct meta_data
{
void* function;
void* instance;
std::vector<type> param_types;
};
std::map<std::string, meta_data> ftable;
Я хотел бы вызвать функции на этой карте, учитывая имена функций и параметры как строки. Моя проблема заключается не в преобразовании параметров (например, с boost::lexical_cast
), а в том, что вы boost::lexical_cast
нужный тип указателя функции и вызвали функцию. Если я разрешу 8 типов и максимум 8 параметров, это уже много ветвей в моем коде. Чего я хочу избежать (псевдокод):
switch (md.param_types.size()) { case 0: cast function pointer, call it break; case 1: switch (md.param_types[0]) { case t_int: int param = boost::lexical_cast(param_strings[0]); cast function pointer, call with param case ... } break; case 2: switch (md.param_types[0]) { case t_int: int param = boost::lexical_cast(param_strings[0]); switch (md.param_types[1]) {...} // second param type.. } break; case n... }
Это очень быстро взрывается с количеством параметров и возможных типов. Я ищу какое-то решение в соответствии с (псевдокодом):
for (auto& p : paramter_strings)
{
convert p to a variable of matching type (type id comes from meta_data).
store value
}
call function with stored values
т.е. нет ветвления для вызова функции. Как я могу сделать это с наименьшим количеством кода шаблона (возможно, с поддержкой произвольного количества параметров)? Вы можете думать об этом как о создании привязок к пользовательскому языку сценария.
Я придумал один подход.
Предположим, что у вас есть вектор unsigned int V, и вы знаете, что каждый элемент вектора является неотрицательным числом, которое меньше N (или, скажем, 20). Вот что вы бы назвали, чтобы изменить вектор V на положительное целое число:
n = sequence_to_code(V,N); // or n = encode(V,20U);
Вот код.
long sequence_to_long(const std::vector<unsigned int> & L,
unsigned long n) {
long result = 0L;
std::vector<unsigned int>::const_iterator w=L.begin(),e=L.end();
if(w!=e) {
result += (*w)+1;
unsigned long the_pow = n;
unsigned int i = 1U;
++w;
while(w!=e) {
result += (*w+1)*(the_pow);
++w;++i;the_pow *= n;
}
}
return result;
}
На самом деле я, вероятно, должен был вернуть "unsigned long".
Кроме того, вы можете использовать эту же процедуру с программой, которая создает текстовый файл. Этот текстовый файл будет содержать код C++. Предположим, вы создали "the_defines.hpp". Я проиллюстрирую пример...
Например, скажем, что t_int = 0; t_double = 1, d_str = 2 и существует только три типа. Тогда "the_define.hpp" может быть файлом:
#define TYPE_EMPTY 0U
#define TYPE_INT 1U
#define TYPE_DOUBLE 2U
#define TYPE_STR 3U
#define TYPE_INT_INT 4U
#define TYPE_DOUBLE_INT 5U
то этот код можно использовать следующим образом:
std::vector<unsigned int> L;
// add entries to L
long n = sequence_to_long(L,3UL);
switch(n) {
case TYPE_INT:
std::cout << "an integer\n";
break;
case TYPE_INT_DOUBLE:
std::cout << "two args; first is an int; second is a double\n:
break;
}
Конечно, вы можете создать текстовый файл с кодом для очень длинного перечисления (если вы хотите избежать #define). Например,
enum class extended_type {
type_int,
type_double,
type_str,
type_int_int,
type double_int,
и так далее.
Вы также можете написать программу, которая создает (один или несколько) текстовых файлов. Эти текстовые файлы также будут иметься в коде C++. Например, ваш созданный файл может быть:
swtich(n) {
case empty:
FILLIN
break;
case t_int:
FILLIN
break;
и так далее до
case t_str_str:
FILLIN;
break;
}
Я бы также рекомендовал кастинг с помощью встроенной функции или обычной функции. Например,
inline int inside_int(foo f) {
const bar & b = * reinterpret_cast<const bar *>(f.pointer());
return b.d_x;
}
Я рекомендую это из-за DRY (не повторяйте себя) и возможность поиска всех экземпляров функции.
Я знаю, что эта программа не обрабатывает ошибки (переполнение, нулевые указатели и т.д.),
отметка