Шаблоны с переменными числами, вывод типов и std :: function

Я пытаюсь создать функцию-шаблон, в которую можно передать какую-либо другую функцию с любым типом и количеством параметров и привязать ее к std::function. Мне удалось это сделать:

#include <iostream>
#include <functional>

int foo(int bar)
{
  std::cout << bar << std::endl;
  return bar;
}

template <typename Ret, typename... Args>
std::function<Ret (Args...)> func(std::function<Ret (Args...)> f)
{
  return f;
}

int main()
{
  //auto barp = func(foo); // compilation error
  auto bar  = func(std::function<void (int)>(foo));

  bar (0); // prints 0
}

Я хотел бы просто вызвать auto barp = func(foo); и определить типы, но эта строка дает следующие ошибки компиляции:

error: no matching function for call to ‘func(void (&)(int))’
    auto barp = func(foo);
                        ^
note: candidate is:
note: template<class Ret, class ... Args> std::function<_Res(_ArgTypes ...)> func(std::function<_Res(_ArgTypes ...)>)
 std::function<Ret (Args...)> func(std::function<Ret (Args...)> f)
                              ^
note:   template argument deduction/substitution failed:
note:   mismatched types ‘std::function<_Res(_ArgTypes ...)>’ and ‘int (*)(int)’
   auto barp = func(foo);
                       ^

Почему он пытается сопоставить std::function<_Res(_ArgTypes ...)> с int (*)(int)? Я чувствую, что мне нужно каким-то образом заставить компилятор расширять _Res(_ArgTypes ...) до int(int), но как?


person Tarc    schedule 13.09.2015    source источник
comment
Почему вы вообще переходите на std::function? std::function - это класс стирания типа: он берет информацию о типе того, из чего он создан, и стирает большую часть ее. Вывод типа берет его аргумент, выводит его тип и генерирует код. Вы просите определить, до какого типа нужно что-то стереть. Это похоже на создание доспехов, из которых создается оружие, чтобы стрелять по владельцу; Стирание типа - это противоположность вывода типа во многих смыслах. Это очень редко бывает хорошей идеей. Это возможно, но у C ++ мало причин делать это простым. У вас есть практический пример использования?   -  person Yakk - Adam Nevraumont    schedule 14.09.2015
comment
А как насчет перегрузок или списков переменных аргументов? Это возможно только при некоторых обстоятельствах. Однако вы можете использовать такие вещи, как boost function_traits.   -  person tahsmith    schedule 14.09.2015
comment
@Yakk, я пытался закодировать функцию мемоизации в том смысле, что она будет возвращать вызываемый объект с теми же параметрами и возвращаемым типом своего аргумента, который будет вызывать исходную функцию, за исключением того, что он будет искать предыдущие вычисленные значения (для сохранения в подходящей карте). Я бы использовал std::function для хранения исходной функции. Не уверен, возможно ли это.   -  person Tarc    schedule 14.09.2015
comment
@Tarc Почему бы просто не сохранить исходный вызываемый объект? Обратите внимание, что имя функции может иметь перегрузки, а вызываемый объект - более одного набора допустимых параметров. См. здесь, где описан мемоизатор с одним аргументом, который работает только с указателями функций. здесь - более общий мемоизатор с рекурсивной обработкой.   -  person Yakk - Adam Nevraumont    schedule 14.09.2015


Ответы (3)


Функция не является std::function, она может быть преобразована в единицу. Однако вы можете вывести аргументы функции, исключая двусмысленность в отношении перегрузок.

#include <iostream>
#include <functional>

int foo(int bar)
{
  std::cout << bar << std::endl;
  return 0;
}

// Will cause error.
//int foo(double); 

template <typename Ret, typename... Args>
std::function<Ret (Args...)> func(Ret f(Args...))
{
  return f;
}

int main()
{
  auto bar = func(foo);
  bar (0); // prints 0
}

То, что вы хотите сделать с исходным std::function, похоже на это, что более очевидно не работает:

template<typename T>
struct A
{
  A(T);  
};

template<typename T>
void func(A<T> a);

int main()
{
    func(42);
}

42 не является A, хотя его можно преобразовать в один. Однако для преобразования его в единицу потребуется, чтобы T уже был известен.

person tahsmith    schedule 14.09.2015
comment
&foo немного посторонний. Имя функции вне вызывающего выражения неявно преобразуется в указатель функции в C / C ++. - person RamblingMad; 14.09.2015
comment
@CoffeeandCode, да, это изменилось. Я предпочитаю иметь его вообще, так как он нужен для функций-членов. Но здесь это просто дополнительная сложность. - person tahsmith; 14.09.2015

Ваш код семантически эквивалентен (если он скомпилирован) этому:

int foo(int x){
    std::cout << x << std::endl;
    return x;
}

int main(){
    auto bar = [](int x){ return foo(x); };
    bar(0);
}

Не считая части return x, но это всего лишь я исправляю ваше неопределенное поведение.

Ваша функция для возврата std::function крайне не нужна, за исключением, может быть, ради меньшего набора текста.

Вы могли бы так же легко использовать конструктор std::functions без функции-оболочки.

Но все равно.

Чтобы сделать то, что вы хотите, вы должны передать сам указатель функции; без невозможного преобразования.

попробуй это:

int foo(int x){
    return x + 1;
}

template<typename Ret, typename ... Args>
auto func(Ret(*fp)(Args...)) -> std::function<Ret(Args...)>{
    return {fp};
}

int main(){
    auto bar = func(foo);
    std::cout << bar(0) << std::endl; // outputs '1'
}

Причина, по которой ваш код не работает, заключается в попытке неявного преобразования, когда вы передаете аргумент в func.

Как я уже сказал, ваш код в настоящее время семантически эквивалентен приведенному выше примеру с использованием лямбда-выражений. Я настоятельно рекомендую просто использовать лямбда-выражения везде, где требуется перенос функций! Они гораздо более гибкие и являются основной частью языка, а не функцией библиотеки.

Помните, что лямбда-выражения без захвата могут быть преобразованы в указатели на функции; так что следующее соответствует:

int main(){
    int(*bar)(int) = [](int x){ return x + 1; };
    std::cout << bar(0) << std::endl;
}

и чтобы иметь такую ​​же функциональность, как вы хотите в своем посте, мы могли бы написать что-то вроде этого:

int main(){
    auto func = +[](int x){ return x + 1; };

    std::cout << "foo(5) = " << func(5) << std::endl;

    func = [](int x){ return x * 2; };

    std::cout << "bar(5) = " << func(5) << std::endl;
}

Обратите внимание, что мы не возимся с объявлениями указателей функций или типами библиотек? Намного приятнее для всех читать / писать. В этом примере следует обратить внимание на унарный оператор +; чтобы выполнить преобразование в указатель на функцию перед присвоением его переменной. На самом деле он выглядит очень функциональным, чего, похоже, вы и пытаетесь достичь здесь.

person RamblingMad    schedule 14.09.2015

Попробуйте развернуть функтор (лямбда и std :: function) через его operator():

#include <iostream>
#include <functional>

int foo(int bar)
{
    std::cout << bar << std::endl;
    return 0;
}

template<typename /*Fn*/>
struct function_maker;

template<typename RTy, typename... ATy>
struct function_maker<RTy(ATy...)>
{
    template<typename T>
    static std::function<RTy(ATy...)> make_function(T&& fn)
    {
        return std::function<RTy(ATy...)>(std::forward<T>(fn));
    }
};

template<typename /*Fn*/>
struct unwrap;

template<typename CTy, typename RTy, typename... ATy>
struct unwrap<RTy(CTy::*)(ATy...) const>
    : function_maker<RTy(ATy...)> { };

template<typename CTy, typename RTy, typename... ATy>
struct unwrap<RTy(CTy::*)(ATy...)>
    : function_maker<RTy(ATy...)> { };

template<typename T>
auto func(T f)
    -> decltype(unwrap<decltype(&T::operator())>::make_function(std::declval<T>()))
{
    return unwrap<decltype(&T::operator())>::make_function(std::forward<T>(f));
}

int main()
{
    //auto barp = func(foo); // compilation error
    auto bar = func(std::function<void(int)>(foo));

    auto bar2 = func([](int)
    {
        // ...
    });

    bar(0); // prints 0
}

Демо

person Denis Blank    schedule 14.09.2015