Я пытаюсь написать обертку make_function
, который как std::make_pair
может создать std::function
объект из подходящих вызываемых объектов.
Как make_pair
для указателя функции foo
, auto f0 = make_function(foo);
создает std::function
функциональный объект f0
правильной подписи типа.
Просто чтобы прояснить, я не против иногда давать параметры типа make_function
в случае, если трудно (или невозможно) полностью вывести тип из параметров.
То, что я до сих пор придумал (код ниже), прекрасно работает для лямбд, некоторых указателей на функции и функторов (я не рассматривал летучие компоненты). Но я не мог заставить его работать на std::bind
или же std::bind<R>
Результаты. В коде ниже
auto f2 = make_function(std::bind(foo,_1,_2,_3)); //not OK
не будет компилироваться / работать, с gcc 4.8.1. Я предполагаю, что я не захватил operator()
для bind
результат правильно, но я не уверен, как это исправить.
Любая помощь в том, как исправить этот случай или улучшение в других угловых случаях приветствуется.
Мой вопрос, конечно, как исправить ошибку в приведенном ниже примере.
Для справки, один из случаев, когда я использую эту обертку, можно найти по этому вопросу: Как сделать так, чтобы функции C ++ 11 принимали функции<> параметры принимают лямбды автоматически. Если вы не одобряете использование std::function
или мой конкретный способ его использования, пожалуйста, оставьте свои комментарии в этом посте и обсудите технические вопросы здесь.
— РЕДАКТИРОВАТЬ —
Из некоторых комментариев я узнал, что это связано с проблемой неоднозначности (неоднозначность оператора вызова функции () std::bind
Результаты). Как указано в ответе @Mooing Duck, решение состоит в том, чтобы явно указывать типы параметров. Я обновил код, чтобы объединить три функции в ответе @Mooing Duck (с небольшим изменением параметров типа), чтобы make_function
Оболочка теперь может обрабатывать / выводить тип однозначных случаев, как и раньше, и разрешать указание полной сигнатуры типа при наличии неоднозначности.
(Мой оригинальный код для однозначных случаев: https://stackoverflow.com/a/21665705/683218 и может быть проверен на: https://ideone.com/UhAk91):
#include <functional>
#include <utility>
#include <iostream>
#include <functional>
using namespace std;
// For generic types that are functors, delegate to its 'operator()'
template <typename T>
struct function_traits
: public function_traits<decltype(&T::operator())>
{};
// for pointers to member function
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
enum { arity = sizeof...(Args) };
typedef function<ReturnType (Args...)> f_type;
};
// for pointers to member function
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) > {
enum { arity = sizeof...(Args) };
typedef function<ReturnType (Args...)> f_type;
};
// for function pointers
template <typename ReturnType, typename... Args>
struct function_traits<ReturnType (*)(Args...)> {
enum { arity = sizeof...(Args) };
typedef function<ReturnType (Args...)> f_type;
};
template <typename L>
static typename function_traits<L>::f_type make_function(L l){
return (typename function_traits<L>::f_type)(l);
}
//handles bind & multiple function call operator()'s
template<typename ReturnType, typename... Args, class T>
auto make_function(T&& t)
-> std::function<decltype(ReturnType(t(std::declval<Args>()...)))(Args...)>
{return {std::forward<T>(t)};}
//handles explicit overloads
template<typename ReturnType, typename... Args>
auto make_function(ReturnType(*p)(Args...))
-> std::function<ReturnType(Args...)> {
return {p};
}
//handles explicit overloads
template<typename ReturnType, typename... Args, typename ClassType>
auto make_function(ReturnType(ClassType::*p)(Args...))
-> std::function<ReturnType(Args...)> {
return {p};
}
// testing
using namespace std::placeholders;
int foo(int x, int y, int z) { return x + y + z;}
int foo1(int x, int y, int z) { return x + y + z;}
float foo1(int x, int y, float z) { return x + y + z;}
int main () {
//unambuiguous
auto f0 = make_function(foo);
auto f1 = make_function([](int x, int y, int z) { return x + y + z;});
cout << make_function([](int x, int y, int z) { return x + y + z;})(1,2,3) << endl;
int first = 4;
auto lambda_state = [=](int y, int z) { return first + y + z;}; //lambda with states
cout << make_function(lambda_state)(1,2) << endl;
//ambuiguous cases
auto f2 = make_function<int,int,int,int>(std::bind(foo,_1,_2,_3)); //bind results has multiple operator() overloads
cout << f2(1,2,3) << endl;
auto f3 = make_function<int,int,int,int>(foo1); //overload1
auto f4 = make_function<float,int,int,float>(foo1); //overload2
return 0;
}
Проблема в том, что ваш код не обрабатывает лямбда-выражения, привязку или функционал должным образом, ваш код предполагает, что все они не принимают параметров. Чтобы справиться с этим, вам нужно указать типы параметров:
//plain function pointers
template<typename... Args, typename ReturnType>
auto make_function(ReturnType(*p)(Args...))
-> std::function<ReturnType(Args...)>
{return {p};}
//nonconst member function pointers
template<typename... Args, typename ReturnType, typename ClassType>
auto make_function(ReturnType(ClassType::*p)(Args...))
-> std::function<ReturnType(Args...)>
{return {p};}
//const member function pointers
template<typename... Args, typename ReturnType, typename ClassType>
auto make_function(ReturnType(ClassType::*p)(Args...) const)
-> std::function<ReturnType(Args...)>
{return {p};}
//qualified functionoids
template<typename FirstArg, typename... Args, class T>
auto make_function(T&& t)
-> std::function<decltype(t(std::declval<FirstArg>(), std::declval<Args>()...))(FirstArg, Args...)>
{return {std::forward<T>(t)};}
//unqualified functionoids try to deduce the signature of `T::operator()` and use that.
template<class T>
auto make_function(T&& t)
-> decltype(make_function(&std::remove_reference<T>::type::operator()))
{return {std::forward<T>(t)};}
Переменные:
int func(int x, int y, int z) { return x + y + z;}
int overloaded(char x, int y, int z) { return x + y + z;}
int overloaded(int x, int y, int z) { return x + y + z;}
struct functionoid {
int operator()(int x, int y, int z) { return x + y + z;}
};
struct functionoid_overload {
int operator()(int x, int y, int z) { return x + y + z;}
int operator()(char x, int y, int z) { return x + y + z;}
};
int first = 0;
auto lambda = [](int x, int y, int z) { return x + y + z;};
auto lambda_state = [=](int x, int y, int z) { return x + y + z + first;};
auto bound = std::bind(func,_1,_2,_3);
тесты:
std::function<int(int,int,int)> f0 = make_function(func); assert(f0(1,2,3)==6);
std::function<int(char,int,int)> f1 = make_function<char,int,int>(overloaded); assert(f1(1,2,3)==6);
std::function<int(int,int,int)> f2 = make_function<int,int,int>(overloaded); assert(f2(1,2,3)==6);
std::function<int(int,int,int)> f3 = make_function(lambda); assert(f3(1,2,3)==6);
std::function<int(int,int,int)> f4 = make_function(lambda_state); assert(f4(1,2,3)==6);
std::function<int(int,int,int)> f5 = make_function<int,int,int>(bound); assert(f5(1,2,3)==6);
std::function<int(int,int,int)> f6 = make_function(functionoid{}); assert(f6(1,2,3)==6);
std::function<int(int,int,int)> f7 = make_function<int,int,int>(functionoid_overload{}); assert(f7(1,2,3)==6);
std::function<int(char,int,int)> f8 = make_function<char,int,int>(functionoid_overload{}); assert(f8(1,2,3)==6);
http://coliru.stacked-crooked.com/a/a9e0ad2a2da0bf1f Единственная причина, по которой ваша лямбда была успешной, заключается в том, что она неявно преобразовывалась в указатель функции, потому что ваш пример не захватывает никакое состояние. Обратите внимание, что мой код требует типы параметров для перегруженных функций, функций с перегруженным оператором () (включая связывание), но теперь он может выводить все не перегруженные функции.
decltype
строки сложные, но они используются для определения типов возвращаемых данных. Обратите внимание, что в NONE из моих тестов мне нужно указать тип возвращаемого значения. Давай сломаться make_function<short,int,int>
вниз, как будто T
является char(*)(short, int, int)
:
-> decltype(t(std::declval<FirstArg>(), std::declval<Args>()...))(FirstArg, Args...)
`std::declval<FirstArg>()` is `short{}` (roughly)
-> decltype(t(short{}, std::declval<Args>()...))(FirstArg, Args...)
`std::declval<Args>()...` are `int{}, int{}` (roughly)
-> decltype(t(short{}, int{}, int{})(FirstArg, Args...)
`t(short{}, int{}, int{})` is an `int{}` (roughly)
-> decltype(short{})(FirstArg, Args...)
`decltype(int{})` is `int`
-> int(FirstArg, Args...)
`FirstArg` is still `short`
-> int(short, Args...)
`Args...` are `int, int`
-> int(short, int, int)
So this complex expression merely figures out the function's signature
well, that should look familiar...
В общем, вы не можете решить это без строгого ограничения, что бы вы ни передавали make_function
вызывается только с одной подписью.
Что вы собираетесь делать с чем-то вроде:
struct Generic
{
void operator()() { /* ... */ }
void operator()() const { /* ... */ }
template<typename T, typename... Ts>
T operator()(T&& t, Ts&&...) { /* ... */ }
template<typename T, typename... Ts>
T operator()(T&& t, Ts&&...) const { /* ... */ }
};
C ++ 14 общих лямбд будет иметь ту же проблему.
Подпись в std::function
основано на том, как вы планируете его называть, а не на том, как вы его создаете / назначаете.
Вы не можете решить это за std::bind
либо, поскольку это имеет неопределенную арность:
void foo() { std::cout << "foo()" << std::endl; }
//...
auto f = std::bind(foo);
f(); // writes "foo()"f(1); // writes "foo()"f(1, 2, 3, 4, 5, 6); // writes "foo()"
Основная причина, почему вы хотите иметь возможность конвертировать лямбды в std::function
потому что вам нужны две перегрузки, каждая из которых принимает разные подписи.
Хороший способ решить эту проблему включает std::result_of
,
Предположим, вы создаете структуру управления циклом, которая принимает лямбда или другой функционал. Если этот функционал возвращает void, вы хотите выполнить бесконтрольный цикл. Если он вернется bool
или тому подобное, вы хотите зациклить, пока он возвращает true
, Если он вернется enum ControlFlow
Вы хотите обратить внимание на ControlFlow
возвращаемое значение (continue
или же break
, сказать). Рассматриваемая функция принимает либо элемент, проходящий итерацию, и, необязательно, некоторые дополнительные данные (индекс в итерации, может быть некоторая мета-информация о местоположении элемента и т. Д.).
std::result_of
позволит вам притвориться, что вы вызываете переданный тип с другим количеством аргументов. Затем класс черт может выяснить, какая из вышеуказанных сигнатур является «наилучшим соответствием», и затем направить его к реализации, которая обрабатывает эту сигнатуру (возможно, обернув «более простые» случаи в лямбду и вызывая более сложные случаи).
Наивно, твой make_function
Может ли эта проблема, потому что вы могли бы просто перегружать различные std::function< blah(etc) >
случаев. Но с auto
параметры, поступающие по трубе, и std::bind
уже делает совершенную пересылку, это обрабатывает только самые легкие случаи.
std::result_of
классы черт (и, возможно, связанные концепции соответствия и requires
пункты) и отправка тегов (или SFINAE в крайнем случае).
Большим недостатком является то, что вам в конечном итоге придется управлять заказом переопределения самостоятельно, вручную. Я мог видеть некоторую утилиту в вспомогательных классах, где вы предоставляете список подписей для сопоставления, и он либо производит boost::variant
или вы также производите канонический вывод и метод преобразования в этот канонический вывод.
Краткий ответ? std::bind
Реализация — это детали, специфичные для реализации, но она может включать в себя эквивалент идеальной пересылки пакетов с переменными параметрами, и, как таковая, не подходит для вас «получить адрес одного-единственного конкретного operator()
«Техника, которую вы используете.
В качестве другого примера:
template <typename A,typename B>
vector<B> map(std::function<B (A)> f, vector<A> arr) {
vector<B> res;
for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
return res;
}
должно быть написано как:
template<typename expression>
using result = typename std::result_of<expression>::type;
template<typename expression>
using decayed_result = typename std::decay<result<expression>>::type;
template <typename function,typename B>
vector<decayed_result<function(B)>> map(function&& f, vector<A> const& arr) {
vector<decayed_result<function(B)>> res;
res.reserve( arr.size() );
for (A const& a : arr) {
res.push_back( f(a) );
}
return res;
}
снова, result_of
это правильное решение, не превращая вещи без необходимости std::function
,
За fold_right
мы получаем:
template<bool b, typename T=void>
using EnableIf = typename std::enable_if<b,T>::type;
template<typename function, typename src, typename dest>
EnableIf<
std::is_convertible< result<function(src, dest)>, dest >::value,
std::vector<dest>
>
fold_right( function&& f, std::vector<src> const& v, dest initial )
который снова пропускает стирание любого типа на f
, И если вы действительно хотите сделать стирание типа на f
, ты можешь сделать:
template<typename T> struct identity { typedef T type; };
template<typename T> using do_not_deduce = typename identity<T>::type;
template<typename src, typename dest>
std::vector<dest> fold_right( do_not_deduce< std::function<dest(src,dest)> > f, std::vector<src> const& v, dest init );
std::function
это стирание типа объект. Вы печатаете erase, потому что хотите использовать тип там, куда не хотите переносить тип. Вывод из типа, какой тип результирующего объекта стирания типа вы должны создать, почти всегда является неправильным ответом, потому что у вас есть вся зависимость не типизированных стертых случаев и вся неэффективность стирания типа.
make_function
Результат зависит от полного типа источника, что делает вывод типа стирания практически полностью бесполезным.