Вложенные выражения привязки

Это следующий вопрос к моему предыдущему вопросу.

#include <functional>

int foo(void) {return 2;}

class bar {
public:
    int operator() (void) {return 3;};
    int something(int a) {return a;};
};

template <class C> auto func(C&& c) -> decltype(c()) { return c(); }

template <class C> int doit(C&& c) { return c();}

template <class C> void func_wrapper(C&& c) { func( std::bind(doit<C>, std::forward<C>(c)) ); }

int main(int argc, char* argv[])
{
    // call with a function pointer
    func(foo);
    func_wrapper(foo);  // error

    // call with a member function
    bar b;
    func(b);
    func_wrapper(b);

    // call with a bind expression
    func(std::bind(&bar::something, b, 42));
    func_wrapper(std::bind(&bar::something, b, 42)); // error

    // call with a lambda expression
    func( [](void)->int {return 42;} );
    func_wrapper( [](void)->int {return 42;} );

    return 0;
}

Я получаю ошибки компиляции в заголовках С++:

functional:1137: error: invalid initialization of reference of type ‘int (&)()’ from expression of type ‘int (*)()’
functional:1137: error: conversion from ‘int’ to non-scalar type ‘std::_Bind<std::_Mem_fn<int (bar::*)(int)>(bar, int)>’ requested

func_wrapper (foo) должен выполнять func (doit (foo)). В реальном коде он упаковывает функцию для выполнения потока. func выполняет функцию, выполняемую другим потоком, doit находится между ними, чтобы проверять необработанные исключения и очищать. Но дополнительное связывание в func_wrapper вызывает проблемы...

Ответы

Ответ 1

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

В этом случае более полезно посмотреть на полную ошибку и экземпляры шаблонов, которые приводят к этому. Например, ошибка, напечатанная моим компилятором (GCC 4.4), заканчивается следующими строками:

test.cpp:12:   instantiated from ‘decltype (c()) func(C&&) [with C = std::_Bind<int (*(int (*)()))(int (&)())>]’
test.cpp:16:   instantiated from ‘void func_wrapper(C&&) [with C = int (&)()]’
test.cpp:22:   instantiated from here
/usr/include/c++/4.4/tr1_impl/functional:1137: error: invalid initialization of reference of type ‘int (&)()’ from expression of type ‘int (*)()’

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

Первый экземпляр шаблона в func_wrapper, ясно показывает, какой тип компилятор вывел из фактического параметра foo в func_wrapper(foo). Я лично ожидал, что это будет указателем функции, но на самом деле это функция reference.

Второй экземпляр шаблона вряд ли читаем. Но немного перепутав с std::bind, я узнал, что формат текстового представления GCC для функции привязки является примерно:

std::_Bind<RETURN-TYPE (*(BOUND-VALUE-TYPES))(TARGET-PARAMETER-TYPES)>

Так разрывая его на части:

std::_Bind<int (*(int (*)()))(int (&)())>
// Return type: int
// Bound value types: int (*)()
// Target parameter types: int (&)()

Здесь начинаются несовместимые типы. По-видимому, хотя c в func_wrapper является ссылкой на функцию, он превращается в функцию указатель после передачи на std::bind, что приводит к несовместимости типа. В этом случае std::forward вообще не имеет значения.

Мое рассуждение здесь состоит в том, что std::bind только, кажется, заботится о значениях, а не о ссылках. В C/С++ нет такой вещи, как значение функции; там только ссылки и указатели. Поэтому, когда ссылка на функцию разыменовывается, компилятор может только осмысленно дать вам указатель на функцию.

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

func_wrapper<int (*)()>(foo);

Или более короткое решение, явно беря адрес функции:

func_wrapper(&foo); // with C = int (*)()

Я вернусь к вам, если узнаю вторую ошибку.:)

Ответ 2

В начале, пожалуйста, позвольте мне представить 2 ключевых момента:

  • a: при использовании вложенного std:: bind сначала выполняется оценка внутреннего std:: bind, а возвращаемое значение будет заменено на свое место, а внешний std:: bind оценивается. Это означает, что std::bind(f, std::bind(g, _1))(x) выполняется так же, как и f(g(x)). Внутренний std:: bind предполагается обернуть std:: ref, если внешний std:: bind хочет функтор, а не возвращаемое значение.

  • b: ссылка на r-значение не может быть правильно перенаправлена ​​на функцию с помощью std:: bind. И причина уже подробно проиллюстрирована.

Итак, давайте посмотрим на вопрос. Наиболее важной функцией здесь может быть func_wrapper, который предназначен для выполнения трех целей:

  • Перспективная пересылка функционального шаблона функции doit сначала,
  • затем используя std:: bind, чтобы сделать doit как закрытие,
  • и позволяя func function template исполнять функтор, возвращаемый std:: bind, наконец.

В соответствии с пунктом b цель 1 не может быть выполнена. Таким образом, пусть забудьте о совершенной функции переадресации, а функция doit должна принять опорный параметр l-value.

В соответствии с пунктом а цель 2 будет выполняться с помощью std:: ref.

В результате окончательная версия может быть:

#include <functional>

int foo(void) {return 2;}

class bar {
public:
    int operator() (void) {return 3;};
    int something(int a) {return a;};
};

template <class C> auto func(C&& c) -> decltype(c()) { return c(); }

template <class C> int doit(C&/*&*/ c)    // r-value reference can't be forwarded via std::bind
{
    return c();
}

template <class C> void func_wrapper(C&& c)
{
    func(std::bind(doit<C>,
                   /* std::forward<C>(c) */ // forget pefect forwarding while using std::bind
                   std::ref(c)) // try to pass the functor itsself instead of its return value
        );
}

int main(int argc, char* argv[])
{
    // call with a function pointer
    func(foo);
    func_wrapper(foo);  // error disappears

    // call with a member function
    bar b;
    func(b);
    func_wrapper(b);

    // call with a bind expression
    func(std::bind(&bar::something, b, 42));
    func_wrapper(std::bind(&bar::something, b, 42)); // error disappears

    // call with a lambda expression
    func( [](void)->int {return 42;} );
    func_wrapper( [](void)->int {return 42;} );

    return 0;
}

Но, если вы действительно хотите достичь целей 1 и 2, как? Попробуйте следующее:

#include <functional>
#include <iostream>

void foo()
{
}

struct bar {
    void operator()() {}
    void dosomething() {}
};

static bar b;

template <typename Executor>
void run(Executor&& e)
{
    std::cout << "r-value reference forwarded\n";
    e();
}

template <typename Executor>
void run(Executor& e)
{
    std::cout << "l-value reference forwarded\n";
    e();
}

template <typename Executor>
auto func(Executor&& e) -> decltype(e())
{
    return e();
}

template <bool b>
struct dispatcher_traits {
    enum { value = b };
};

template <typename Executor, bool is_lvalue_reference>
class dispatcher {
private:
    static void dispatch(Executor& e, dispatcher_traits<true>)
    {
        run(e);
    }

    static void dispatch(Executor& e, dispatcher_traits<false>)
    {
        run(std::ref(e));
    }

public:
    static void forward(Executor& e)
    {
        dispatch(e, dispatcher_traits<is_lvalue_reference>());
    }
};

template <typename Executor>
void func_wrapper(Executor&& e)
{
    typedef dispatcher<Executor,
                       std::is_lvalue_reference<Executor>::value>
        dispatcher_type;

    func(std::bind(&dispatcher_type::forward, std::ref(e)));
}

int main()
{
    func_wrapper(foo);   // l-value
    func_wrapper(b);  // l-value
    func_wrapper(bar());  // r-value
    func_wrapper(std::bind(&bar::dosomething, &b));  // r-value
    func_wrapper([](){});  // r-value
}

Позвольте мне объяснить некоторые моменты:

  • Чтобы уменьшить количество операторов return, сменив функцию-подпись от int() на void().
  • Шаблоны функций 2 run() используются для проверки того, переадресован ли исходный параметр функтора или нет.
  • dispatcher_traits собирается отображать константу bool для ввода.
  • Лучше назовите диспетчер:: forward, чтобы отличаться от диспетчера:: отправка, или вы должны вызвать шаблон std:: bind с диспетчером:: прямой подписью.