Как правильно объявить метод, который принимает только лямбда?

#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 Возьмите a std::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». И именно поэтому у нас есть дубликаты. Так что, казалось бы, несвязанный вопрос может указывать непосредственно на правильную информацию без необходимости повторять ее в другом месте. Вопрос, помеченный как дубликат, — это неплохо.