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