funcall в C++: объявление функций, которые принимают функции в качестве параметров

В Интернете есть много примеров использования STL для передачи функций или объектов функций в качестве параметров, например, в std::count .

Как мне написать свои собственные функции, которые принимают такие аргументы?

В качестве простого примера скажем, что мой класс:

struct Foo{
 int val=0;
 int methodinc()const{return val+1};
 }

Я хотел бы определить функцию funcall, например:

int funcall (foo arg, function f) 
  {return f(arg); }

где объявление «функции» — это то, в чем я не уверен, среди прочего. Термин «funcall» пришел из Лиспа, где (funcall f a b c) просто применяет f к аргументам a b c.

Тогда что-то вроде этого должно работать:

Foo ff;

funcall(ff,Foo::methodinc); // should return 1
funcall(ff, [](Foo x) {return x.val+1;}) // should return 1

Каковы простые способы сделать это?

Я пишу их как помощники по отладке, «funcall» будет использоваться как часть реализации моих собственных, таких как аналоги моей собственной структуры данных count, remove-if, transform и другие подобные функции STL, которые принимают аргументы функций. Но я не хочу писать сложные шаблонные выражения для определения своего кода.


Первоначальные ответы на этот вопрос предполагают, что вся концепция объявления и использования аргументов функции немного неясна, по крайней мере, для меня. Возможно, прежде чем обратиться к funcall, еще проще было бы просто передать функциональный аргумент другой функции, а не использовать его. Например, в C++ для подсчета вектора v мне нужно написать

std::count(v.begin, v.end(), [](int j){return j>3})

Как можно написать счетчик, который всегда считает весь вектор, чтобы:

 mycount(v,[](int j){return j>3})

такой же как выше? И может ли этот «mycount» работать с указателями на функции-члены вместо лямбда-выражений?

Этот вопрос в основном такой же, как вопрос «funcall», но без требования фактического вызова переданного функционального объекта.


person kdog    schedule 17.05.2014    source источник
comment
Вам нужна функция, которая принимает произвольное количество аргументов? Это тоже возможно с С++ 11. funcall(f, a, b, c) и т. д.   -  person dyp    schedule 17.05.2014
comment
Хорошо, я добавил эти исправления, спасибо за исправление. Нет, мне не нужна переменная arg #s.   -  person kdog    schedule 17.05.2014


Ответы (2)


Обычно функция шаблон лучше всего подходит для такой потребности в гибкости:

template <typename F, typename T>
auto funcall(T && t, F f) -> decltype(f(std::forward<T>(t))
{
    return f(std::forward<T>(t));
}

Вместо завершающего возвращаемого типа и decltype вы также можете использовать трейт result_of:

template <typename F, typename T>
typename std::result_of<F(T&&)>::type funcall(T && t, F f)
{
    return f(std::forward<T>(t));
}

Или, в C++14, вы можете просто сказать decltype(auto) funcall(T && t, F f) без завершающего типа возвращаемого значения, и оно будет выведено автоматически.

Основная причина сделать F аргументом выведенного шаблона, а не фиксированным типом (например, std::function<R(T)>), состоит в том, чтобы позволить вам вызывать funcall напрямую с лямбда-выражениями и bind/mem_fn выражениями, которые имеют неизвестные типы. объекта std::function довольно дорого по сравнению с ним.

person Kerrek SB    schedule 17.05.2014
comment
Есть ли еще более простой способ, скажем, если я знаю, что аргументы funcall всегда являются аргументом типа Foo и другой функцией, которая принимает Foo и возвращает int? Я не беспокоюсь об эффективности, просто пишу короткий, простой и понятный код. - person kdog; 17.05.2014
comment
Зачем брать F по значению? @kdog В этом случае вас волнует низкая производительность? - person Yakk - Adam Nevraumont; 17.05.2014
comment
@Yakk: Мех. Переместите семантику, скопируйте elision, вы называете это. Я мог бы изменить его на ссылку, я полагаю. - person Kerrek SB; 17.05.2014
comment
Нет, я не забочусь о производительности здесь. Кстати, как мне написать оболочку, скажем, для std::count(), чтобы mycount(vec,f) был бы таким же, как mycount(vec.begin(),vec.end(),f) для лямбды ф? - person kdog; 17.05.2014
comment
@kdog: template <typename C, typename F> void mycount(C && c, F f) { mycount(c.begin(), c.end(), f); }?! - person Kerrek SB; 17.05.2014
comment
Ах, это выглядит очень полезно... но вы имеете в виду count не mycount во втором использовании термина, а также mycount возвращает int (или size_t), а не void, верно? - person kdog; 17.05.2014
comment
Вы знаете, что я делаю тогда, Керрек, если это разумно - я мог бы просто полностью отказаться от funcall и просто преобразовать свои структуры данных на лету в vec, а затем передать функцию. Таким образом, мне не нужно беспокоиться о std ::forward и несколько && (которые, как я предполагаю, я могу игнорировать, верно, и не использовать?). - person kdog; 17.05.2014
comment
@kdog, пожалуйста, старайтесь задавать вопросы по теме. Если у вас совершенно другая проблема (например, mycount), сначала поищите ответ в сети, попробуйте что-нибудь сами, а если это не удастся, задайте новый вопрос о переполнении стека. - person Yakk - Adam Nevraumont; 17.05.2014
comment
Ты прав, Якк. Но мотивация была прежней: найти самый простой способ использовать функциональное значение в качестве параметра. Так что это не совсем другая проблема. Я надеялся, что вопрос mycount будет упрощенной версией вопроса funcall. Я не нашел в Интернете ничего полезного для решения проблемы написания функций, которые принимают аргументы функций. Практически каждая статья фокусируется только на использовании лямбда-выражений для вызова функций STL, а многие из остальных написаны до C++11. Но я отредактирую исходный пост, чтобы уточнить. - person kdog; 17.05.2014
comment
@kdog: Возможно, лучше задать новый отдельный вопрос. Очень сложно поддерживать полезную систему вопросов и ответов, если вопрос продолжает двигаться. Нет ничего плохого в заданном вопросе. - person Kerrek SB; 17.05.2014
comment
Вы правы, вопрос. Но подождите, последний вопрос/комментарий. Можем ли мы реализовать funcall без замысловатых шаблонов, просто создав вектор из одного элемента, а затем передав этот вектор и функцию ввода в std::transform, а затем вернув первый элемент вектора? (Теперь уверен, что это будет работать для функций-членов вместо лямбда-выражений). Что-то вроде ...vector‹Foo› v{arg}; вернуть преобразование (v.begin(), v.end(), f))[0] - person kdog; 18.05.2014

C++ — необычайно мощный и сложный язык. В нем вы можете делать все, что вы можете делать в Лиспе, включая реализацию Лиспа самостоятельно. Проблема в том, что для этого вам придется довольно много узнать о языке и о том, что он может делать. Использование функций в качестве объектов, к сожалению, является одной из самых сложных частей C++.

Есть несколько способов решить вашу проблему. Ответ @Kerrek отличный, но явно выходит за рамки того, к чему вы готовы. Код, представленный в вашем редактировании, предназначен для лямбда, что не обязательно упростит ситуацию.

По своей сути объекты-функции в C++ — это просто указатели. Они выглядят так.

typedef int (*func)(int a, char b);

int f(int aa, char bb) {
  return aa + bb;
}

int main(void) {
  func fv = f;
  int ret = fv(10, ' ');
  printf ("ret=%d", ret);
  return 0;
}

Здесь func — это тип, представляющий вызов функции, f — фактическая функция, а fv — функциональная переменная вызова.

Из этой структуры строится все остальное. С шаблонами компилятор выполняет сопоставление типов, а с лямбда-выражениями вам не нужно придумывать nmes. Под всем этим функции C/C++ — это просто указатели.

Таким образом, ответ заключается в том, что вы можете написать свои собственные функции, которые принимают функции в качестве аргументов, если вы знаете, что эти аргументы являются просто указателями на функции подходящего типа, объявленные, как показано выше.

person david.pfx    schedule 19.05.2014
comment
На самом деле это хороший ответ. На самом деле я написал методы, которые принимают функции-члены в качестве аргументов, и я надеялся легко расширить их на лямбда-выражения, но так и не понял, как это сделать. - person kdog; 20.05.2014
comment
Должна быть хорошая ссылка на основы функционального программирования на C++. Один, который находится между подходами кулинарной книги, заключается в том, как вы называете преобразование во всех книгах и ссылках, которые я видел, с одной стороны, и загадочный язык стандарта, с другой. Тем более, что лямбда-выражения в теории должны сделать этот стиль программирования популярнее. - person kdog; 20.05.2014
comment
Может быть, достаточно хорошо для плюса? Проблема с C++ в том, что это предельно дырявая абстракция. Вы не сможете эффективно работать на самых высоких уровнях абстракции, пока не освоите нижние уровни. Это оказывается весьма ограничивающим. - person david.pfx; 20.05.2014