Почему нет std:: make_function()?
std::function<>
- полезная оболочка вокруг практически любой вызываемой вещи, включая бесплатные функции, lambdas, функторы, функции-члены, результаты от std::bind
. Однако при создании std::function<>
необходимо явно указать подпись функции, как в
(взято из здесь)
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{ std::cout << i << '\n'; }
struct PrintNum {
void operator()(int i) const
{ std::cout << i << '\n'; }
};
// store a free function
std::function<void(int)> f_display = print_num;
// store a lambda
std::function<void()> f_display_42 = []() { print_num(42); };
// store the result of a call to std::bind
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
// store a call to a member function
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
// store a call to a member function and object
using std::placeholders::_1;
std::function<void(int)> f_add_display2= std::bind( &Foo::print_add, foo, _1 );
// store a call to a member function and object ptr
std::function<void(int)> f_add_display3= std::bind( &Foo::print_add, &foo, _1 );
// store a call to a function object
std::function<void(int)> f_display_obj = PrintNum();
хотя подпись может быть выведена из назначенных объектов. Кажется, что естественным способом избежать этого (что должно быть весьма удобно в сильно шаблоном) является перегруженный шаблон функции make_function
(аналогичный по духу std::make_pair
или std::make_tuple
), когда вышеприведенные примеры просто станут
// store a free function
auto f_display = make_function(print_num);
// store a lambda
auto f_display_42 = make_function([](){ print_num(42);});
// store the result of a call to std::bind
auto f_display_31337 = make_function(std::bind(print_num, 31337));
// store a call to a member function
auto f_add_display = make_function(&Foo::print_add);
// store a call to a member function and object
using std::placeholders::_1;
auto f_add_display2 = make_function(std::bind( &Foo::print_add, foo, _1));
// store a call to a member function and object ptr
auto f_add_display3 = make_function(std::bind( &Foo::print_add, &foo, _1));
// store a call to a function object
auto f_display_obj = make_function(PrintNum());
Еще один возможный случай использования - получить возвращаемый тип для вызываемого объекта любого типа
decltype(make_function(function_object))::return_type;
избегая черт черт в ответе Петра С. на этот вопрос.
Итак, мой вопрос: почему стандарт не предоставляет эту функцию? Может ли make_function
быть реализовано без магии компилятора? Или ему понадобится магия компилятора? (даже тогда остается первый вопрос.)
Ответы
Ответ 1
Как прокомментировано здесь и в другом месте, существует проблема двусмысленности, которая может смутить вывод типов. Вероятно, эти угловые случаи прекратили прием std::make_function
, поскольку он не смог бы устранить двусмысленность, перегрузку или работу с автоматическими преобразованиями типа С++. Другой аргумент против этого, что я вижу много, заключается в том, что std::function
имеет служебные данные (в стирании типа), и многие люди против использования std::function
на этой основе для чего-либо, кроме хранения вызываемых вызовов.
Однако для недвусмысленного случая можно написать make_function
для lambdas и других callables, которые заботятся о методах ввода типов, что позволяет избежать повторения сигнатур типа функции, когда на самом деле нет никакой двусмысленности. Один из способов сделать это (взятый из моего связанного вопроса) выглядит следующим образом:
#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;
}
Ответ 2
class multi_functor
{
public:
void operator()(int) { std::cout << "i'm int" << std::endl }
void operator()(double) { std::cout << "i'm double" << std::endl }
};
int main(void)
{
auto func = make_function(multi_functor());
}
Потому что какой тип func
здесь?
Эта неоднозначность применяется ко всем объектам-функторам (включая bind
возвращаемые значения и lambdas), что сделало бы make_function
пригодным только для указателей на функции.
Ответ 3
Общий случай не может работать. Существуют конкретные случаи (поддерживающие С++ 11 lambdas, но не С++ 14, не поддерживают bind
, поддерживают неперегруженные функции и методы, не поддерживают объекты функций), где вы можете построить make_function
, который "работает". Есть также некоторые функции, которые вы можете написать, которые являются полезными.
make_function
, который "работает", обычно плохая идея.
Просто сохраните копию исходного объекта функции, если вам не нужно преобразовывать его в std::function<?>
. Вам нужно только преобразовать его в std::function<?>
, когда вы уже знаете типы, которые собираетесь переходить к нему, и то, что вы делаете с возвращаемым типом - т.е. Когда вы стираете стили вокруг подписи.
std::function
не является "универсальным держателем для типа функции". Это класс стирания типа, который используется для стирания информации о типе, поэтому вы можете иметь код, который работает на нем равномерно. Если вы выведете подпись из объекта, нет никаких оснований хранить его в std::function
вообще.
Существуют узкие случаи, когда это полезно, когда вы хотите вести себя по-разному на основе типов входных и выходных аргументов аргумента функции, который вы передали. В этом случае ваше средство вычитания подписи могло бы быть полезным: привязка его к std::function
была бы плохой идеей, на мой взгляд, поскольку она связывает два независимых понятия (вычитание подписи и стирание стилей) таким образом, что это редко полезно.
Короче говоря, пересмотреть.
Теперь я упомянул выше, что есть некоторые полезные утилиты, которые можно назвать make_function
. Вот два из них:
template<class...Args, class F>
std::function< std::result_of_t< F&(Args...) >
make_function( F&& f ) {
return std::forward<F>(f);
}
но требует, чтобы вы перечислили аргументы. Он выводит возвращаемое значение.
Этот вариант:
template<class F>
struct make_function_helper {
F f;
template<class...Args>
std::result_of_t< (F&&)(Args...) >
operator()(Args&&...args)&& {
return std::forward<F>(f)(std::forward<Args>(args)...);
}
template<class...Args>
std::result_of_t< (F const&)(Args...) >
operator()(Args&&...args) const& {
return f(std::forward<Args>(args)...);
}
template<class...Args>
std::result_of_t< (F&)(Args...) >
operator()(Args&&...args) & {
return f(std::forward<Args>(args)...);
}
template<class R, class...Args, class=std::enable_if_t<
std::is_convertible<decltype( std::declval<F&>(Args...) ), R>{}
>>
operator std::function<R(Args...)>()&&{ return std::forward<F>(f); }
template<class R, class...Args, class=std::enable_if_t<
std::is_convertible<decltype( std::declval<F&>(Args...) ), R>{}
>>
operator std::function<R(Args...)>()const&{ return f; }
};
template<class F>
make_function_helper<F> make_function(F&&f) { return {std::forward<F>(f)}; }
фактически не выполняет функцию, но позволяет вам вызвать функцию с несколькими перегрузками std::function
и выбрать между ними правильно. Он также может быть запущен в идеальном направлении назад к базовому F
. В примерах 97/100 вы не сможете заметить разницу между этим make_function
и тем, который возвращает фактический std::function
(эти последние случаи являются случаями, когда кто-то ожидает ввести тип std::function
из типа, и безупречные отказы пересылки)
Итак:
int foo(std::function< int(int) >) { return 0; }
int foo(std::function< void() >) { return 1; }
int main() {
std::cout << foo( []{} ) << "\n";
}
не удается скомпилировать, а
int main() {
std::cout << foo( make_function([]{}) ) << "\n";
}
преуспевает. Однако даже этот трюк - это просто исправление дыры в дизайне std::function
, которое, я надеюсь, будет перенесено в пост-концепции std::function
. В этот момент вы можете просто сохранить исходный объект.
В общем случае вы не можете определить единственную гарантированную уникальную подпись для вызываемого объекта x или имени функции x.
В случае вызываемого объекта operator()
может иметь несколько перегрузок. Это можно сделать в С++ 14 с помощью [](auto x)
lambdas и с объектами функций или с возвратом из std::bind
в С++ 03.
С именем функции (или указателем на функцию) имя не соответствует одному объекту (или указателю). Разрешение выполняется, когда оно передается в std::function
, и часто выбирается правильная перегрузка (потому что std::function
принимает указатель R(*)(Args...)
и, возможно, что-то подобное для функций-членов (я не могу вспомнить)).
Делать это с помощью make_function
почти невозможно.
Ответ 4
Обратите внимание, что во всех ваших примерах вы можете просто удалить make_function
и получить тот же результат или фактически более эффективно, потому что для вызова std::function
часто требуется виртуальный вызов. Поэтому первым хорошим моментом было бы препятствовать использованию std::function
, когда это не нужно.
Обычно вы используете std::function
как объект-член какого-либо класса (callback et similia) или как аргумент функции, которая по какой-либо причине не может быть шаблоном. В обоих случаях make_function
было бы бесполезно.
struct Foo
{
std::function<int()> callback
};
Foo x; x.callback = [](){return 0;} // No need for make_function
void bar( std::function<int(int)> f );
bar( [](int i){ return i;} ); // No need for make function.
Есть только один случай, когда я могу подумать, где вы действительно можете извлечь выгоду: a std::function
, инициализированный тернарным оператором:
auto f = val ? make_function( foo ) : make_function( bar );
вероятно, лучше, чем
auto f = val ? std::function<int()>( foo ) : std::function<int()>( bar );
Я считаю, что это довольно редкий случай, поэтому преимущества make_function
минимальны.
Реальный недостаток ИМО заключается в том, что простое существование гипотетического make_function
поощряло бы менее опытных разработчиков использовать std::function
, когда это не обязательно, как вы показываете в своем коде.