привязать связанную функцию в качестве аргумента

У меня есть класс foo с методом bar, который принимает что-то вызываемое (указатель на функцию/функтор). это вызываемое нечто должно быть передано другому методу doit в качестве связанного элемента с третьим методом bar_cb.

#include <functional>
#include <iostream>

class foo {
public:
    template<typename T>
    void bar(T&& t) {
        std::cout << "bar\n";
        doit(std::bind(&foo::template bar_cb<T>, this, std::forward<T>(t)));
    }

    template<typename T>
    void doit(T&& t) {
        std::cout << "doit\n";
        t();
    }

    template<typename T>
    void bar_cb(T&& t) {
        std::cout << "bar_cb\n";
        t();
    }
};


void lala() {
    std::cout << "lala\n";
}

class functor {
public:
    void operator()() {
        std::cout << "functor::operator()\n";
    }
};


int main() {
    foo f;
    functor fn;
    f.bar(fn);
    f.bar(std::bind(lala));  // error

    return 0;
}

Это отлично работает для functors, но не для связанных функций в качестве аргумента для foo::bar (lala в моем примере). Можно ли передать методу неизвестный тип и привязать его в этом методе в качестве аргумента к другому (и если да, то как)?

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

Вот ссылка на пример.


person user1810087    schedule 30.12.2014    source источник
comment
Какую реальную проблему (более высокого уровня) вы пытаетесь решить?   -  person John Zwinck    schedule 30.12.2014
comment
@JohnZwinck проблема в том, что я хочу иметь класс, который принимает функтор или функцию и вызывает другие функторы с этим callable в качестве параметра. реальный код представляет собой вариативный класс шаблона с различными функторами в качестве параметров шаблона. тем не менее, я попытался свести проблему к простому классу без шаблонов, чтобы понять, что не так. также я хочу избежать накладных расходов на std::function.   -  person user1810087    schedule 30.12.2014
comment
Вы столкнулись со специальной обработкой bind выражений привязки. stackoverflow.com/questions/10777421/stdbind-a-bound-function< /а>   -  person T.C.    schedule 30.12.2014
comment
@Т.С. да, я знаю :( я кое-что читал об этом. и это сложно, потому что я недостаточно хорошо понимаю, как bind работает под капотом.   -  person user1810087    schedule 30.12.2014
comment
Возможно, использовать лямбду вместо std::bind? Живой пример. Однако не уверен в последствиях идеальной переадресации.   -  person Igor Tandetnik    schedule 30.12.2014
comment
Написать unbind, который берет выражение привязки и заключает его в тип, который не является выражением привязки? А в остальном ведет себя так же?   -  person Yakk - Adam Nevraumont    schedule 30.12.2014


Ответы (1)


Основная проблема заключается в том, что ваш bar_cb(T&&) не выводит аргумент шаблона, потому что аргумент шаблона фактически указывается при использовании &foo::template bar_cb<X> с некоторым аргументом шаблона X. Однако выражение bind() будет копировать связанную функцию, т. е. оно может иметь или не иметь тип, который будет выведен. Кроме того, std::bind() не будет передавать bind()-выражение, а будет вызывать их!

Самый простой обходной путь — не использовать std::bind() для привязки функции, а использовать лямбда-функцию:

template<typename T>
void bar(T&& t) {
    std::cout << "bar\n";
    doit([=](){ this->bar_cb(t); });
}

При этом давайте компилятору вывести тип корректирующего аргумента для bar_cb() (в C++14 вы можете использовать захват [this,t = std::forward<T>(t)], хотя ваш bar_cb() по-прежнему не увидит rvalue).

Чтобы передать уже bind()-выражение через другое bind()-выражение, не bind() рассматривая внутреннее bind()-выражение как bind()-выражение, вам нужно сделать так, чтобы оно не выглядело bind()-выражением. Вы можете сделать это с помощью тонкой функциональной оболочки:

template <typename Fun>
class unbinder {
    Fun fun;
public:
    template <typename F>
    unbinder(F&& fun): fun(std::forward<F>(fun)) {}
    template <typename... Args>
    auto operator()(Args&&... args) const
        -> decltype(fun(std::forward<Args>(args)...)) {
        return fun(std::forward<Args>(args)...);
    }
};
template <typename Fun>
auto unbind(Fun&& fun)
    -> unbinder<Fun> {
    return unbinder<Fun>(std::forward<Fun>(fun));
}

Поскольку функция, хранящаяся в выражении bind(), будет передаваться по lvalue, вам потребуется другое объявление для вашего bar_cb():

template<typename T>
void bar_cb(T& t) {
    ...
}

При этом вы можете зарегистрировать bind()-выражение, используя

f.bar(unbind(std::bind(lala)));

Если вы хотите использовать f.bar(std::bind(lala)), вам понадобится условное определение bar(): если он получает bind()-выражение, он должен автоматически скрывать тот факт, что это bind()-выражение, применяя unbind() или что-то подобное:

template<typename T>
typename std::enable_if<!std::is_bind_expression<typename std::decay<T>::type>::value>::type
bar(T&& t) {
    std::cout << "bar (non-bind)\n";
    doit(std::bind(&foo::template bar_cb<T>, this, std::forward<T>(t)));
}
template<typename T>
typename std::enable_if<std::is_bind_expression<typename std::decay<T>::type>::value>::type
bar(T&& t) {
    std::cout << "bar (bind)\n";
    doit(std::bind(&foo::template bar_cb<unbinder<T>>, this, unbind(std::forward<T>(t))));
}
person Dietmar Kühl    schedule 30.12.2014
comment
спасибо за этот подробный ответ. я попробую оба. Кстати, есть ли конкретная причина, по которой вы передаете значение в лямбда-функции. - person user1810087; 30.12.2014
comment
Захват связанных аргументов по значению больше напоминает исходную конструкцию с использованием srd::bind() (если вы хотите захватить значение по ссылке с помощью std::bind(), вы должны использовать std::ref()). Кроме того, кажется, что это более надежная отправная точка, поскольку нет проблем на протяжении всего срока службы. Наконец, захват общих аргументов по значению дает пользователю функции возможность переопределить значение по умолчанию, также используя std::ref(). - person Dietmar Kühl; 30.12.2014