LINUX.ORG.RU

передача функции как аргумента


0

2

Господа, такая проблема...

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QString>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    void foo (int (*bar)(int), int);
    int foo2(int);

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    foo(foo2, 4);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::foo(int (*bar)(int), int i)
{
    (*bar)(i);
}

int MainWindow::foo2(int i)
{
    return i;
}
$ qmake
$ make
/usr/bin/uic mainwindow.ui -o ui_mainwindow.h
g++ -c -m64 -pipe -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector --param=ssp-buffer-size=4 -D_FORTIFY_SOURCE=2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt/mkspecs/linux-g++-64 -I. -I/usr/include/QtCore -I/usr/include/QtGui -I/usr/include -I/usr/include/qwt -I. -I. -o main.o main.cpp
g++ -c -m64 -pipe -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector --param=ssp-buffer-size=4 -D_FORTIFY_SOURCE=2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt/mkspecs/linux-g++-64 -I. -I/usr/include/QtCore -I/usr/include/QtGui -I/usr/include -I/usr/include/qwt -I. -I. -o mainwindow.o mainwindow.cpp
mainwindow.cpp: In constructor «MainWindow::MainWindow(QWidget*)»:
mainwindow.cpp:10:16: ошибка: нет подходящей функции для вызова «MainWindow::foo(<unresolved overloaded function type>, int)»
mainwindow.cpp:10:16: замечание: candidate is:
In file included from mainwindow.cpp:1:0:
mainwindow.h:22:10: замечание: void MainWindow::foo(int (*)(int), int)
mainwindow.h:22:10: замечание:   no known conversion for argument 1 from «<unresolved overloaded function type>» to «int (*)(int)»
make: *** [mainwindow.o] Ошибка 1

foo2 у тебя метод класса, поэтому его нельзя так передавать. Вытащи его из класса, тогда будет работать (на счёт использования модификатора static не уверен, давно в плюсах не игрался с такими штуками).

P.S. методам класса (не статическим) неявно передаётся ещё и параметр this имеющий вид ClassType *this. Как именно это реализовано сейчас не скажу ибо с плюсами редко сталкиваюсь.

Norgat ★★★★★ ()
Последнее исправление: Norgat (всего исправлений: 2)
Ответ на: комментарий от v0r0n

И не должно, на сколько я помню. Я же написал - либо вытащи foo2 из класса, либо пробуй поставить static у foo2.

P.S. можно ещё глянуть в сторону C++11x, там лямбды появились, через них такие выкрутасы должно быть легко сделать.

Norgat ★★★★★ ()
class MainWindow(object):
    def __init__(self):
        self.foo(self.foo2, 4)

    def foo(self, bar, i):
        print(bar(i))

    def foo2(self, i):
        return i
anonymous ()

Указатели на функции-члены - это не указатели.

man std::bind1st, std::bind2nd

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

c++11 way уже был?


#include <functional>
#include <iostream>

using namespace std;

void foo(std::function< void (void) > f) {
  f();
}

class A {
public:
  void call_foo() {
    foo(std::bind(&A::bar, this));
  }
  void bar() {
    cout << "A::bar" << endl;
  }
};

int main () {
  A a;
  a.call_foo();
}

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

и ещё лямбду туда же

foo([] { cout << "lambda" <<endl; } );

invy ★★★★★ ()

Хреновый из тебя, батенька, ООПщик, раз ты сишные методы применяешь.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от invy

c++11 way уже был?

А не подскажешь, нет ли в c++11 чего-нибудь, что бы облегчило передачу не статических методов в функции вроде sigaction(2)? А то приходится костыли городить.

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

сделай себе шаблонную функцию

Зачем? Вот есть у меня Class::handler использующий контекст Class и я хочу сделать что-то вроде signal(some_sig, statically(Class::handler)) внутри метода класса или где-то ещё. По идее, анонимная функция с замыканием должна помочь, так как (gcc only):

struct class {
    // ...
    void(*handler)(void *this, int sig);
};

    // closure around `class_obj':
    void handler_(int sig) { class_obj->handler(class_obj, sig); }
    signal(some_sig, handler_);

Class::Caller()::<lambda(int)> в void (*)(int) тоже не хочет превращаться.

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

Указатель на ф-цию и указатель на на метод-член класса - это разные вещи, ибо второе подразумевает наличие объекта, член которого надо вызвать. Если метод MainWindow::foo(...) будет работать только с методами-членами класса MainWindow, то можно использовать нижеследующую конструкцию как основу:

#include <iostream>

class A
{
public:
        int foo(int (A::*bar)(int), int arg)
        {
                return (this->*bar)(arg);
        }
        int foo2(int i)
        {
                return i;
        }
};

int main(int argc, char *argv[])
{
        A a;
        std::cout << a.foo(&A::foo2, 123) << std::endl;
}

Если же метод MainWindow::foo(...) должен принимать на вход указатель на метод-член произвольного класса, то можно, например, использовать адаптер std::mem_fun или std::mem_fun_ref, а для пробрасывания указателя/ссылки на объект, метод которого надо вызвать можно воспользоваться адаптером std::bind1st или std::bind2nd. Все это доступно в «классическом» C++.

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

Камрад, ты не прав! Указатель на функцию - это просто адрес памяти. А вот указаетль на метод - это не только адрес. Там целая структура с информацией о смещении в vtbl. Подробнее о том, что там есть можно прочитать в статейке на рсдн. Ссылку я выше давал. Вот и получается, что указатель на метод в общем случае невозможно преобразовать в указатель на функцию.

Еще не забывай про то, что в метод класса надо как то передавать this. Тот код, который дергает callback в виде функции о this ничего не знает, следовательно передавать ничего не будет.

Получается, что нужна такая цепочка:

  1. внешний код дергает callback некоего враппера, где враппер свой шаблонный класс, а callback - его static метод.
  2. В качестве аргумента в этот callback передается указаетль на инстанс враппера.
  3. Инстанс враппера хранит - указатель на метод который надо вызвать + указатель на инстанс класса, для которого по указателю надо вызвать метод.
  4. Ну собсно по данным из п.3 callback и осуществляет вызов.

Как хранить инстансы врапперов - это уже другой вопрос и зависит от того, как ты хочешь с ними работать.

ЗЫ: вчера ошибся. Шаблонной функции будет мало, так как надо дето контекст хранить.

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

Указатель на функцию - это просто адрес памяти. А вот указаетль на метод - это не только адрес.

Если не брать особо запущенные^W виртуальные случаи, то это обычный указатель вида RetT(*)(classT*, ...), отличается от обычного указателя на функцию только наличием classT* ─ чтобы преобразовать его в RetT(*)(...) нужно всего-то сделать «частичное применение» к this (внутри класса) или к какому-то другому объекту во вне. «Частичное применение» в C или C++ = создание локальной или анонимной функции на стеке или в куче, замыкающей объект класса.

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

Я привёл пример как указатель на произвольный метод может превращаться в указатель на обычную функцию с использованием вложенных функций gcc:

    // RetT(*)(classT*, ...) -> RetT(*)(...):
    RetT fn(...) { return this->meth(this, ...); }
    // ^ capturing `this'.

fn это локальная функция ─ выделяется на стеке (то есть сам код функции), замыкает и помнит про this. Может дёргаться из signal-like функций.

Но g++ не умеет вложенных функций, да и не стандарт это. Поэтому хочется аналогично для лямбд:

    // RetT(Class::meth*)(...) -> RetT(*)(...):
    auto fn = [&, this] (...) -> RetT { return Class::meth(...); }

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

Тот код, который дергает callback в виде функции о this ничего не знает, следовательно передавать ничего не будет.

Его не нужно передавать, он уже замкнут в callback. Настоящие замыкания в функциональных языках (да и в C++ должны) вообще выделяются в куче. Это принципиальная особенность ─ локальные объекты, в том числе замыкания, линейны, то есть на них всегда одна ссылка, их можно выделять на стеке (как это принято в C++ для локальных объектов). Возвращаемые локальные объекты (и замыкания) нелинейный, на них более чем одна ссылка, поэтому они должны выделяться в куче (как это происходит с new) и как-то освобождаться при обнулении числа ссылок (memory pools / regions, ref. counting, gc).

Получается, что нужна такая цепочка

Это я изначально назвал «костыли». Хотя подразумевал совет из C++ FAQ создать глобальный (!) указатель на объект Class, выставлять его в конструкторе и писать статическую обвёртку вокруг Class::handler использующую этот глобальны указатель. В случае с signal ещё ничего, так как обычно объект такого класса и так единственен. Но не всегда это так.

Вообще, изначально я хотел std::to_plain_pointer(Class::meth, this) который сам бы всё разруливал. Это частный случай частичного применения (есть же в С++ <functional>, в конце концов?).

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

но как только лямбда начинает замыкать она перестаёт нормально приводиться к обычному указателю на функцию

И аналогично для std::function.

handler.cc: In member function ‘void Class::signal_(std::function<void(int)>, int
)’:                                                                             
handler.cc:367:69: error: cannot convert ‘std::function<void(int)>’ to ‘__sighand
ler_t {aka void (*)(int)}’ for argument ‘2’ to ‘void (* signal(int, __sighandler_
t))(int)’                                                                       
In file included from handler.cc:363:0:
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional: 
In static member function ‘static void std::_Function_handler<void(_ArgTypes ...)
, _Functor>::_M_invoke(const std::_Any_data&, _ArgTypes ...) [with _Functor = std
::_Bind<std::_Mem_fn<void (Class::*)(int)>(Class*)>, _ArgTypes = {int}]’:       
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional:2
148:6:   instantiated from ‘std::function<_Res(_ArgTypes ...)>::function(_Functor
, typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_R
es(_ArgTypes ...)>::_Useless>::type) [with _Functor = std::_Bind<std::_Mem_fn<voi
d (Class::*)(int)>(Class*)>, _Res = void, _ArgTypes = {int}, typename std::enable
_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_U
seless>::type = std::function<void(int)>::_Useless]’                            
handler.cc:375:58:   instantiated from here
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional:1
778:2: error: no match for call to ‘(std::_Bind<std::_Mem_fn<void (Class::*)(int)
>(Class*)>) (int)’                                                              
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional:1
130:11: note: candidates are:                                                   
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional:1
201:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor
(_Bound_args ...)>::operator()(_Args&& ...) [with _Args = {_Args ...}, _Result = 
_Result, _Functor = std::_Mem_fn<void (Class::*)(int)>, _Bound_args = {Class*}] 
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional:1
215:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor
(_Bound_args ...)>::operator()(_Args&& ...) const [with _Args = {_Args ...}, _Res
ult = _Result, _Functor = std::_Mem_fn<void (Class::*)(int)>, _Bound_args = {Clas
s*}]                                                                            
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional:1
229:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor
(_Bound_args ...)>::operator()(_Args&& ...) volatile [with _Args = {_Args ...}, _
Result = _Result, _Functor = std::_Mem_fn<void (Class::*)(int)>, _Bound_args = {C
lass*}]                                                                         
/usr/lib/gcc/x86_64-redhat-linux/4.6.3/../../../../include/c++/4.6.3/functional:1
243:2: note: template<class ... _Args, class _Result> _Result std::_Bind<_Functor
(_Bound_args ...)>::operator()(_Args&& ...) const volatile [with _Args = {_Args .
..}, _Result = _Result, _Functor = std::_Mem_fn<void (Class::*)(int)>, _Bound_arg
s = {Class*}]                                                                   

блин, они же всё равно создают замыкание, почему бы не дать обычный сишный указатель на неё.

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

Это все тоже самое что и у меня, только через функциональщину )

Твой подход конечно на порядок более краткий и совершенный, но, увы, в С++ не работает. Почему? Ну это вопрос не ко мне, а к разработчикам стандарта.

Я же предлагаю наваять на темплейтах простенький аналог std::function. Пусть он ничего болше не умеет, кроме как замыкать this и, самое главное, будет давать обычный сишный указатель на себя.

А за развернутое объяснение с ссылками спасибо! Годный пример для изучения функциональщины.

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