#c #lambda
#c #лямбда
Вопрос:
В следующем примере мне нужен traverse
метод, который получает обратный вызов. Этот пример отлично работает, как только я ничего не фиксирую []
, потому что лямбда-выражение можно свести к указателю на функцию. Однако в данном конкретном случае я хотел бы получить доступ sum
.
struct Collection {
int array[10];
void traverse(void (*cb)(int amp;n)) {
for(int amp;i : array)
cb(i);
}
int sum() {
int sum = 0;
traverse([amp;](int amp;i) {
sum = i;
});
}
}
Каков правильный способ (без использования каких-либо шаблонов) решить эту проблему? Решение заключается в использовании typename
шаблона следующим образом. Но в этом случае вам не хватает видимости того, что дает траверс на каждой итерации (an int
):
template <typename F>
void traverse(F cb) {
for(int amp;i : array)
cb(i);
}
Комментарии:
1. @max66 итак, правильным ответом на этот вопрос будет: нет, решения без указателей нет.
2. @max66 Я отредактировал свой вопрос для более конкретного аспекта этого.
3. Пожалуйста, прочтите обманку. Если у вас есть какой-то конкретный вопрос, который там не рассматривается, или он не отвечает на ваш вопрос, пожалуйста, отредактируйте вопрос.
4. Вы также можете использовать
std::function
. Это влечет за собой некоторые накладные расходы, но функция больше не будет шаблоном.5. И, наконец, вы не хотите, чтобы ваш метод «принимал только лямбда-выражение». Как насчет обычных функций и объектов, не являющихся лямбда-функциями?
Ответ №1:
Типы лямбда не указаны; нет способа их назвать.
Итак, у вас есть два варианта:
-
Создайте
traverse
шаблон (или возьмите егоauto
, что фактически одно и то же)К счастью, это совершенно нормальная и обычная вещь.
-
traverse
Возьмите astd::function<void(int)>
. Это влечет за собой некоторые накладные расходы, но, по крайней мере, означает, что функция не обязательно должна быть шаблоном.
Но в этом случае вам не хватает видимости того, что дает traverse на каждой итерации (int)
Мы не склонны считать это проблемой. Я понимаю, что указание этого в типе функции более удовлетворительно и понятно, но, как правило, комментария достаточно, потому что, если обратный вызов не предоставляет an int
, вы все равно получите ошибку компиляции.
Ответ №2:
С указателями на функции можно использовать только лямбда-выражения без захвата. Поскольку каждое определение лямбда-выражения имеет свой собственный тип, вы должны использовать параметр шаблона во всех местах, где вы принимаете лямбда-выражения, которые захватывают.
Но в этом случае вам не хватает видимости того, что дает traverse на каждой итерации (int ).
Это можно легко проверить с помощью SFINAE или еще проще с помощью концепций в C 20. И чтобы упростить его еще на один шаг, вам даже не нужно определять концепцию и использовать ее позже, вы можете напрямую использовать специальное требование как это (это приводит к двойному использованию requires
ключевого слова:
struct Collection {
int array[10];
template <typename F>
// check if F is "something" which can be called with an `intamp;` and returns void.
requires requires ( F f, intamp; i) { {f(i)} -> std::same_as<void>; }
void traverse(F cb)
{
for(int amp;i : array)
cb(i);
}
// alternatively you can use `std::invocable` from <concepts>
// check if F is "something" which can be called with an `intamp;`, no return type check
template <std::invocable<intamp;> F>
void traverse2(F cb)
{
for(int amp;i : array)
cb(i);
}
int sum() {
int sum = 0;
traverse([amp;](int amp;i) {
sum = i;
});
return sum;
}
};
Комментарии:
1. Это намного проще записать
template<invocable<int> F>
, чем использоватьrequires
предложение long . Хотя вам нужно будет использоватьstd::invoke
при его вызове.2. Нет, я имею в виду
invocable
концепцию , поэтому она работает в заголовке шаблона в коде, который я ввел. И кого волнует возвращаемый тип? Передача пользователем функции, которая возвращает значение, не причинит никакого вреда.3. @NicolBolas ОК, не знал о 🙂 Добавит его! Спасибо
Ответ №3:
В вашем случае у вас есть несколько способов объявить обратный вызов на C :
Указатель на функцию
void traverse(void (*cb)(int amp;n)) {
for(int amp;i : array)
cb(i);
}
Это решение поддерживает только типы, которые могут распадаться на указатель на функцию. Как вы упомянули, лямбды с захватами этого не сделают.
Шаблон имени типа
template <typename F>
void traverse(F cb) {
for(int amp;i : array)
cb(i);
}
Он принимает что угодно, но, как вы заметили. код трудно читать.
Стандартные функции (C 11)
void traverse(std::function<const void(int amp;num)>cb) {
for(int amp;i : array)
cb(i);
}
Это наиболее универсальное решение с небольшими накладными расходами.
Не забудьте включить <functional>
.
Комментарии:
1. Я не понимаю. Все это описано в вопросе, который был помечен как цель dupe, которая, по вашим словам, даже не ответила на ваш вопрос.
2. Возможно, на него дан ответ, но примеры не были упомянуты. Я не эксперт в C упомянутый вами dup был труден для чтения и понимания. Мне потребовалось время, чтобы понять, что
std::function
это то, что я искал (и я до сих пор не понимаю, сколько накладных расходов).3. Это разумно, но вопрос по-прежнему обманчив. И там есть много примеров. Можете ли вы уточнить, каким образом ваш ответ добавляет дополнительную информацию? (и если вы чувствуете, что это так, я предлагаю написать ответ на самом dupe).
4. Для меня это все еще другой вопрос. Один из них касается подписи функции в шаблонах, что требует знания того, что такое подпись и что такое шаблон. Мой касается обратных вызовов в методах. Я почти уверен, что кто-то, кто ищет примеры обратных вызовов, нажмет на мой вопрос, а не на этот dup. Я вонг?
5. @nowox: » Я почти уверен, что кто-то, кто ищет примеры обратных вызовов, нажмет на мой вопрос, а не на этот dup». И именно поэтому у нас есть дубликаты. Так что, казалось бы, несвязанный вопрос может указывать непосредственно на правильную информацию без необходимости повторять ее в другом месте. Вопрос, помеченный как дубликат, — это неплохо.