LINUX.ORG.RU

С++, указатели на функции

 ,


0

5

Доброго времени суток!

Как известно, в С++ функции можно перегружать и даже делать на них указатели. Но как передавать указатель на функцию неопределенного типа (не на класс и не на любой другой объект, только на функцию, число и типы параметров которой заранее неизвестны) в другую функцию? Думаю насчет assert-ов или variable templates. Решение пока не придумал, но мб кто-то уже сталкивался и знает решение?

Смежный вопрос - как возвращать указатель на подобную функцию?

Имеется ввиду нечто вот такое, но как видно, вышеописанные проблемы в этом коде не решены:

/*нет проверки на то, что fp действительно является функцией!*/
template<class function_pointer> 
void register_function( std::string name,
			type* prototype,//указатель на массив типов параметров
			type  ret,//тип возвращаемого значения
			function_pointer fp//указатель на функцию
);

/*Указатель-то вернется, но совсем не ясно, как им воспользоваться.
К тому же, я хз, как сделать передачу аргументов, которые лежат в двусвязном списке в обычную функцию навроде int foo(int a, int b, double c). список аргументов надо как-то динамически создавать (задача, обратная vararg)...*/
void* find_func(const std::string & name);
★★

Последнее исправление: aido (всего исправлений: 1)

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

Мне показалось, что такое невозможно будет сделать.

aido ★★
() автор топика

передавай/возвращай void*, приводи к нужному типу, вызывай
как иначе вызвать функцию не зная ее тип?

t1nman
()
Ответ на: комментарий от t1nman

ну допустим, но как мне составить для нее список параметров?

Изначально, положим, есть вот такой массив (для простоты, вместо списка):

int N;
std::cin>>N;
int *a=new int[N];
for(int i=0;i<N;++i)
{
   a[i]=i;
}

И есть перегруженная функция f. Как мне в нее вставлять аргументы (элементы массива) по-одному или как сформировать список аргументов, имея этот массив? Передавать указатель на этот массив - не вариант, так как это всего лишь игрушечный пример, а дальше будет все гораздо сложнее. Есть, конечно, костыльный и очень некрасивый вариант - выделять побайтово маллоком память, закинуть туда друг за другом все параметры, которые функции должны передаться, завести все функции одного и того же вида - int func (void*args, linked_list* l) и напрямую работать с памятью. Но это какое-то извращенное и очень неудобное решение.

aido ★★
() автор топика

Интересно, как бы тут помог assert.

anonymous
()

У себя в сях решил так:

PutToStorageINTEGER(Handler, 3423);
PutToStorageSTRING(Handler, "dfsdfsd");

CallFunc(pointertofunc(/* Здесь пусто */))
CallbackFunc(void)
{
   int i = GetFromStorage(MyHandler, INTEGER);
   char * momomom = GetFromStorage(MyHandler, STRING);
}

Под капотом в хранилище - юнион с флагом о типе.

someoneelsenotme
()
Ответ на: комментарий от aido

Не надо никаких void*. Для аргументов тебе нужно реализовать динамическую типизацию. Что-то наподобии boost::any или QVariant.

Для вызова функции по имени тебе нужна какая-нибудь фабрика или map хранящий объекты с указателем на нужную тебе функцию и умеющие ее вызывать приводя аргументы к нужному типу.

four_str_sam
()

Как-то так:

std::function<std::vector<Object>(std::vector<Object>)> 
Функция распаковывает параметры, как ей надо и запаковывает то, что возвращает. Соответственно вызывающая функция распаковывает распаковывает то, что получила от функции.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 1)

но вообще, необходимость в динамической типизации, за редким исключением, свидетельствует о плохой архитектуре программы

next_time ★★★★★
()

Не надо так делать. Делай функцию, принимающую список в качестве единственного аргумента.

Miguel ★★★★★
()

То, что ты делаешь называется интроспекцией. С++ крайне плох в этом плане (потому, что все резолвится статически на этапе компиляции).

Поэтому, что ты можешь сделать, это создать массив с лямбдами/std::function, которые будут дергать твою функцию правильно. Вот, например так (код чисто концептуальный, может даже нескомпилироваться):

// вот тут можно заюзать что-то более продвинутое, наподобии QVariant
typedef void * FunctionResult;
typedef void * FunctionArgument;

typedef std::map<std::string, FunctionArgument> FunctionArguments;
typedef std::function<FunctionResult (const FunctionArguments &)> FunctionHelper;

class MetaFunction {
public:
    MetaFunction(const std::string &name, FunctionHelper helper) :
        m_helper(helper),
        m_name(name)
    {}

    FunctionResult call(const FunctionArguments &args) {
        return this->m_helper(args);
    } 

private:
    std::string    m_name;
    FunctionHelper m_helper;
};

void* find_func(const std::string & name) { /* ... */ }

auto meta = MetaFunction("find_func", 
    [] (const FunctionArguments &args) -> FunctionResult {
        void *result = find_func(args["name"]);
        // тут надо подумать как конвертнуть результат в FunctionResult
        return FunctionResult(result); 
    }
);

auto result = meta.call({
    { "name", FunctionArgument("needle") }
});

KennyMinigun ★★★★★
()
Последнее исправление: KennyMinigun (всего исправлений: 2)

А зачем это вообще может понадобиться?

Допустим, ты реализовал такое и вернул пользователю твоей find_func указатель на какую-то функцию. Теперь, во-первых, как пользователь твоей find_func поймёт, с какими аргументами её вызвать? Во-вторых, как он будет использовать её результат (заранее неизвестный)? И в-третьих, если он не может её вызвать (по причинам, описанным в «во-первых» и в «во-вторых»), то как он вообще должен её использовать?

То же самое верно и для принятия неизвестной функции в качестве аргумента.

devsdc ★★
()
Последнее исправление: devsdc (всего исправлений: 1)

Я конечно вообще крестовое днище и не шарю, но точно так же как на сишке:

#include <functional>
#include <iostream>

template<typename T> T __get(void * & ptr) {
  T ret =  *((T *)ptr); ptr = (char *)ptr + sizeof(T);
  return ret;
}

template<typename T> T __set(void * & ptr, T val) {
  T * ret = new(ptr) T(val); ptr = (char *)ptr + sizeof(T);
  return *ret;
}

template<typename... Args> inline void pass(Args&&...) {}


template<typename ret_type, typename ... args> void add(void * & ptr, ret_type f(args...), args ... arg) {
  std::function<int(void * &)> __f = [=](void * & ptr) -> auto {
    return f(__get<args>(ptr)...);
  };
  __set<decltype(__f)>(ptr, __f);
  pass(__set<args>(ptr, arg)...);
}


int f1(int a) {
  return fprintf(stderr, "%d -> ", a);
}


int f1(int a, float b) {
  return fprintf(stderr, "%d %f -> ", a, b);
}

int main(void) {
  void * ptr = malloc(100500), * begin = ptr;
  add<int>(ptr, f1, 10, 10.f);
  add<int>(ptr, f1, 10);
  add<int>(ptr, f1, 123, 24124.f);
  add<int>(ptr, f1, 123678);
  add<int>(ptr, f1, 221241, 11230.f);
  add<int>(ptr, f1, 323, 112340.f);
  add<int>(ptr, f1, 4235235);
  add<int>(ptr, f1, 212566, 1552130.f);
  add<int>(ptr, f1, 1235561233);
  add<int>(ptr, f1, 212323);
  add<int>(ptr, f1, 1234);
  add<int>(ptr, f1, 55124);
  void * end = ptr;
  while((std::cerr << __get<std::function<int(void * &)>>(begin)(begin) << std::endl), begin != end);
}

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

Эта вся лапша заталкивается в классик и ptr/int и прочее выпиливаются. Гет запихивается в итератор - будет тебе стлненько. Там ваши раишные заморочки обработаешь сам.

Что-то в очередной раз начал писал на крестах и начинаю припоминать - почему в своё время я их покинул.

registrant27492
()
Ответ на: комментарий от aido

Я так и не понял что тебе надо - что такое «статический массив, содержащий указатели на функции (в том числе и перегруженные) вместе с информацией о них (тип ретурна и список типов параметров)» и как оно должно работать?

registrant27492
()
Ответ на: комментарий от registrant27492

Не говорил, но

один на каждый тип

тоже не то, т.к. сочетание <Возвращаемый тип><Некая функция> — уникальная сущность.

pS
()
Ответ на: комментарий от pS

тоже не то, т.к. сочетание <Возвращаемый тип><Некая функция> — уникальная сущность.

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

registrant27492
()

Если статически - клади их в std::tuple (в сыром виде или std::function) и всего делов то. А если в динамическом - возьми лучше жабоскрипт.

CatsCantFly
()
Последнее исправление: CatsCantFly (всего исправлений: 1)
Ответ на: комментарий от next_time

но вообще, необходимость в динамической типизации, за редким исключением, свидетельствует о плохой архитектуре программы

Удваиваю.

Мне интересно, для какой задачи это понадобилось (сильно подозреваю, что можно обойтись и без того, что написано в ОП), но этого как раз мы не знаем...

hobbit ★★★★★
()

на функцию, число и типы параметров которой заранее неизвестны

Гораздо интереснее, как ты собрался вызывать такую функцию?

no-such-file ★★★★★
()
Ответ на: комментарий от registrant27492

Хочу нечто похожее (таблицу встроенных функций в единообразном формате):

enum type
{
    VOID,
    INT_T,
    DOUBLE_T,
    WORD_T,
    OBJECT,
    FUNCTION
};

struct arg_proto//for func_proto
{
    int number;
    type type_val;
    std::string var_name;
    std::shared_ptr<arg_proto> prev_arg;
    std::shared_ptr<arg_proto> next_arg;
};

struct func_proto//for comparing with proto
{
    std::string name;
    arg_proto prototype;
    type ret;
    void* (*pbody)(void* arg);//pointer to the function
};

static const func_proto func_table[]=
{
    {"exit",{VOID},VOID,[](){exit(EXIT_SUCCESS);}},
    {"help",{VOID},VOID,show_help},
	/*General User Info*/
    {"s",{VOID},WORD_T,get_status},
    {"hist",{VOID},WORD_T,get_history},
    {"histat",{VOID},WORD_T,get_history_statistics},
	/*OS operations*/
    {"pwd", {VOID}, WORD_T, pwd},
    {"cd",  {FILENAME_T}, VOID, emulate_cd(const char*)},
    {"ls",  {VOID}, WORD_T, emulate_ls},
    {"ls",  {FILENAME_T}, WORD_T, emulate_ls(const char*)},
    {"ls",  {WORD_T, FILENAME_T}, WORD_T, emulate_ls(const char*, const char*)},
    {"cat", {FILENAME_T}, WORD_T, emulate_cat(const char*)}
};

Здесь есть пара важных ошибок, которые С++ никогда не пропустит, ибо прямое нарушение стандартов.

aido ★★
() автор топика
Последнее исправление: aido (всего исправлений: 1)
Ответ на: комментарий от no-such-file

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

aido ★★
() автор топика

По сути ты пытаешься взять на себя работу компилятора. Оно тебе надо? Сделай набор аргументов максимальным и объявляй все функции с ним. А в некотором месте просто вызывай find_func(«myfunc», arg1, arg2, arg3)(). Соответственно какие-то аргументы в конкретных функциях будут не нужны - так не используй их и всё.

anonymous
()
Ответ на: комментарий от anonymous

find_func(«myfunc»)(arg1, arg2, arg3) конечно же.

anonymous
()
Ответ на: комментарий от anonymous

Ну вот я и строю счас интерпретатор=). Он, правда, будет тупым как пробка, но больше от него и не требуется)

aido ★★
() автор топика
Ответ на: комментарий от someoneelsenotme

Опять эти убогие потуги балаболов. Ну давай:

PutToStorageINTEGER(Handler, 'ой');//куллстори. Что будешь делать?
int i = GetFromStorage(MyHandler, INTEGER);//что же ты достёшь инт-то? А как же флоат?
float i = GetFromStorage(MyHandler, INTEGER);//что будешь делать?

//а если вдруг перепутались аргументы, либо перепутался MyHandler? Что будешь делать?

С - смешно. лсность оправдывается «не перепутать», при этом выкатывая портянку, где перепутать можно в сотне других мест. Ты уж определить - перепутать можно, либо нельзя? Либо хотя-бы сложно. Твои потуги не дали даже «сложно», поэтому не работают.

registrant27492
()
Ответ на: комментарий от registrant27492

Eсть одна специфическая задачка, для которой надо сделать удобную консоль управления. Операции, которые может сделать юзер по большей части однотипные и их можно и нужно встроить в саму консоль. Просто писать прогу, которая будет хавать тонну опций - неудобно как для прогера, так и для пользователя, делать для нее конфиг с этими самыми опциями - неудобно из-за статичности исполнения (в самом начале задали, че-то посчитали и вышли то есть, но ведь может статься и так, что во время исполнения задачи нужно время от времени следить за ходом выполнения), использовать перл/питон для написания даже такого простого интерпретатора - какой-то оверхед, ведь дальше у меня пойдет дикий матан и заточка под железо. Пусть уж всё тогда будет на С++ единообразно. Надо будет - гуй на питоне прикрутить можно будет позже. Поэтому и хочу, чтобы интерпретатор парсил корректно грамматику (уже сделал) и дальше искал эти токены по списку функций и объектов и дергал их. Ну стандартно в общем для интерпретаторов/компиляторов.

aido ★★
() автор топика
Ответ на: комментарий от aido

Поэтому и хочу, чтобы интерпретатор парсил корректно грамматику (уже сделал) и дальше искал эти токены по списку функций и объектов и дергал их. Ну стандартно в общем для интерпретаторов/компиляторов.

Вовсе не поэтому. Для разбора строк типа «команда аргументы» не надо городить ИИ и даже парсер не нужен. И не надо за команды делать всю работу и готовить аргументы. Пусть каждая команда для себя читает из потока ввода.

anonymous
()
Ответ на: комментарий от anonymous

ну там не «команда аргументы», так как еще немного из ООП есть. синтаксис стал похож на до-диезовский.

aido ★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.