Подтвердить что ты не робот

Почему нет 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 быть реализовано без магии компилятора? Или ему понадобится магия компилятора? (даже тогда остается первый вопрос.)

4b9b3361

Ответ 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, когда это не обязательно, как вы показываете в своем коде.