2015-01-07 2 views
12

std::function<> - полезная обертка вокруг практически любой вызываемой вещи, включая бесплатные функции, lambdas, функторы, функции-члены, результаты от std::bind. Однако, при создании std::function<>, необходимо явно указать сигнатуру функции, как и в (взятую из here)Почему нет std :: make_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; 

избежать магии черты в ответе Петра С. к this question.

Итак, мой вопрос: почему стандарт не предоставляет эту функцию? Может ли make_function реализоваться без магии компилятора? Или ему понадобится магия компилятора? (Даже тогда первый вопрос остается.)

+1

Может быть, потому, что вывод функции очень тяжелый (невозможно?) В C++. Тем более, что функции C++ могут быть (a) шаблонами (b) принимать широкий диапазон типов из-за неявных преобразований. – Rufflewind

+0

@ Rufflewind Вы говорите, что невозможно реализовать 'make_function'? Если да, пожалуйста, продемонстрируйте это в ответе. – Walter

+0

Я не могу это продемонстрировать, но я попытался вывести типы лямбда ранее и не смог. Это не обязательно означает, что это невозможно, но может быть умный трюк, о котором я не знаю. – Rufflewind

ответ

6

Как прокомментировано здесь и в другом месте, существует проблема двусмысленности, которая может смутить ввод типов.Вероятно, эти угловые случаи прекратили прием std::make_function, так как он не смог бы устранить двусмысленность, перегрузку или работать с автоматическими преобразованиями типа C++. Другой аргумент против этого, что я вижу много, заключается в том, что std::function имеет накладные расходы (в стирании типа), и многие люди против использования std::function на этой основе для чего-либо, кроме хранения вызываемых вызовов.

Однако для недвусмысленного случая можно написать make_function для lambdas и других callables, которые заботятся о методах ввода типов, что позволяет избежать повторения сигнатур типа функции, когда на самом деле нет двусмысленности. Один из способов сделать это (взятое из my related question) выглядит следующим образом:

#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; 
} 
14
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 доступным только для указателей функций.

+0

Как это относится к лямбда? – Walter

+1

@Walter '[] (auto x) {return x;}' – Yakk

+0

@Walter Поскольку lambdas juste генерирует классы с перегруженным 'operator()' и некоторым состоянием, см. Http://en.cppreference.com/w/ cpp/language/lambda – Drax

1

Обратите внимание, что во всех ваших примерах вы можете просто удалить 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. 

Существует только один случай, я мог думать о том, где вы действительно можете иметь преимущество: а 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, когда это не обязательно, как показано в коде.

+0

'... во всех ваших примерах вы можете просто удалить make_function и получить тот же результат' - это зависит от того, как вы определяете« тот же ». Это не совсем то же самое для большинства примеров, таких как 'auto f_display = make_function (print_num);', если объект 'std :: function' в переменной' auto' предназначен для создания, как в OP. – tinlyx

3

Общий случай не может работать. Существуют конкретные случаи (поддерживающие C++ 11 lambdas, но не C++ 14, не поддерживают bind, поддерживают неперегруженные функции и методы, не поддерживают объекты функций), где вы можете построить , который «работает». Есть также некоторые функции, которые вы можете написать, которые полезны.

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() может иметь несколько перегрузок. Это можно сделать в C++ 14 с [](auto x) lambdas и с объектами функций или с возвратом от std::bind в C++ 03.

С именем функции (или указателем на функцию) имя не соответствует одному объекту (или указателю). Разрешение выполняется, когда оно передается в std::function, и правильная перегрузка часто выбирается (потому что std::function принимает указатель R(*)(Args...) и может быть что-то подобное для функций-членов (я не могу вспомнить)).

Выполнение этого с помощью make_function практически невозможно.

+0

Люди часто пропускают точку 'std :: function' и придумывают всевозможные смехотворно бессмысленные машины. +1 для добавления смысла в эту вещь. –

Смежные вопросы