Вложенные выражения связывания - PullRequest
13 голосов
/ 30 апреля 2010

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

#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;
}

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

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 все портит ...

Ответы [ 2 ]

2 голосов
/ 08 апреля 2013

В начале, пожалуйста, позвольте мне представить 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, которая предназначена для выполнения 3 целей:

  1. Идеально перенаправляет функтор в шаблон функции doit,
  2. затем с помощью std :: bind сделать doit в качестве замыкания,
  3. и позволить шаблону функции func выполнить функтор, возвращенный std :: bind, наконец.

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

В соответствии с пунктом a, цель 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
}

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

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

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

В этом случае более полезно взглянуть на полную ошибку и экземпляры шаблона, которые к ней приводят. Например, ошибка, напечатанная моим компилятором (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). Я лично ожидал, что это будет указатель на функцию, но на самом деле это функция ссылка .

Второй экземпляр шаблона едва читаем. Но немного поиграв с 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 ++ нет такого понятия, как значение функции; есть только ссылки и указатели. Поэтому, когда ссылка на функцию разыменована, компилятор может только дать вам указатель на функцию.

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

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

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

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

Я вернусь к вам, если когда-нибудь выясню вторую ошибку. :)

...