Передача лямбда-функции в шаблонную функцию с выведенными типами [дубликат]

Вопрос или проблема

Передача лямбда-функции в шаблонную функцию с выведенными типами [дубликат]

У меня есть следующий пример с двумя версиями функции make, которая принимает другую функцию и выводит типы аргументов. Затем она создает структуру с шаблонным типом Signature с этой функцией.

enum class SomeEnum {
     ONE = 1,
     TWO = 2,
};

template <SomeEnum _Type, typename _Result, typename... _Args>
struct Signature {
  static constexpr SomeEnum Type = _Type;
  using Result = _Result;
  using Args = std::tuple<_Args...>;

  std::function<_Result (_Args...)> func;
};

template <
    SomeEnum _Type,
    typename _Result,
    typename... _Args>
Signature<_Type, _Result, _Args...> make(
    std::function<_Result (_Args...)> func) {
  return Signature<_Type, _Result, _Args...>{.func = func};
}

template <SomeEnum _Type, typename _Result, typename... _Args>
Signature<_Type, _Result, _Args...> make(
    _Result (*func)(_Args...)) {
  return Signature<_Type, _Result, _Args...>{
      .func = std::move(func)};
}

void foo(int x, std::string y, int z) {
}

auto sig1 = make<SomeEnum::ONE>(&foo);

auto sig2 = make<SomeEnum::TWO>([](int x, std::string y, int z) {
});

Эта версия работает:

auto sig1 = make<SomeEnum::ONE>(&foo);

но эта версия:

auto sig2 = make<SomeEnum::TWO>([](int x, std::string y, int z) {
});

Не работает

main.cpp:40:13: ошибка: нет подходящей функции для вызова 'make'
auto sig2 = make<SomeEnum::TWO>([](int x, std::string y, int z) {
            ^~~~~~~~~~~~~~~~~~~
main.cpp:23:37: примечание: кандидат шаблона игнорируется: не удалось сопоставить 'std::function<_Result (_Args...)>' с '(лямбда на main.cpp:40:33)'
Signature<_Type, _Result, _Args...> make(
                                    ^
main.cpp:29:37: примечание: кандидат шаблона игнорируется: не удалось сопоставить '_Result (*)(_Args...)' с '(лямбда на main.cpp:40:33)'
Signature<_Type, _Result, _Args...> make(
                                    ^
1 ошибка сгенерирована.

Однако, если я явно укажу тип, то это работает:

std::function<void(int x, std::string y, int z)> l = [](int x, std::string y, int z) {
};

auto sig2 = make<SomeEnum::TWO>(l);

Как я могу сделать, чтобы эта версия:

auto sig2 = make<SomeEnum::TWO>([](int x, std::string y, int z) {
});

работала? Я бы хотел иметь возможность просто передать лямбду без дополнительной типизации и позволить коду выведать типы.

<div class="s-prose js-post-body" itemprop="text">
    <p><code>std::function</code> поддерживает CTAD (через свои <a href="https://en.cppreference.com/w/cpp/utility/functional/function/deduction_guides" rel="nofollow noreferrer">руководства по выводу</a>), но CTAD не применяется к параметрам функции.</p>
    <p>Поэтому тип вашего параметра должен быть шаблонным (вместо <code>std::function&lt;__&gt;</code>):</p>
    <pre><code>template &lt;typename F&gt; void foo(F &amp;&amp;func) {bar(std::function(std::forward&lt;F&gt;(func)));}

Где bar принимает std::function<R(P...)>, как ваша первая перегрузка make().

</div>

Ответ или решение

Ваша проблема связана с тем, как C++ обрабатывает шаблоны и автоматическое выведение типов, особенно когда вы передаете лямбда-функции в шаблонные функции. В данном случае, у вас есть несколько перегрузок функции make, которые ожидают разные типы аргументов, и проблема возникает, когда вы передаете лямбда-функцию.

Как было отмечено, std::function поддерживает CTAD (сечение для автоматического вывода типов), но это не применимо к параметрам функции. Вам необходимо изменить подход к обработке аргументов, чтобы разрешить передавать лямбда-функции без явного указания типа.

Вот как можно переписать вашу функцию make, чтобы она принимала лямбда-функции:

#include <iostream>
#include <functional>
#include <tuple>
#include <string>
#include <utility>

enum class SomeEnum {
    ONE = 1,
    TWO = 2,
};

template <SomeEnum _Type, typename _Result, typename... _Args>
struct Signature {
    static constexpr SomeEnum Type = _Type;
    using Result = _Result;
    using Args = std::tuple<_Args...>;

    std::function<_Result(_Args...)> func;
};

// Изменяем первую версию make
template <SomeEnum _Type, typename F>
auto make(F&& func) {
    using ResultType = decltype(std::declval<F>()(std::declval<typename std::tuple_element<0, typename Signature<_Type, void, int, std::string, int>::Args>::type>()));
    return Signature<_Type, ResultType, int, std::string, int>{
        .func = std::function<ResultType(int, std::string, int)>(std::forward<F>(func))
    };
}

// Вторая версия осталась без изменений
template <SomeEnum _Type, typename _Result, typename... _Args>
Signature<_Type, _Result, _Args...> make(_Result (*func)(_Args...)) {
    return Signature<_Type, _Result, _Args...>{
        .func = std::move(func)};
}

void foo(int x, std::string y, int z) {
    std::cout << "foo called with: " << x << ", " << y << ", " << z << std::endl;
}

int main() {
    auto sig1 = make<SomeEnum::ONE>(&foo);

    auto sig2 = make<SomeEnum::TWO>([](int x, std::string y, int z) {
        std::cout << "Lambda called with: " << x << ", " << y << ", " << z << std::endl;
    });

    // Примеры вызова функций через сигнатуры
    sig1.func(1, "hello", 10);
    sig2.func(2, "world", 20);

    return 0;
}

Объяснение

  1. Обработка обобщенных лямбда-функций: Мы изменили функцию make, чтобы принимать любой объект (включая лямбда-функции). Этот подход универсален, так как он не требует явного указания типа функции, что позволяет компилятору извлекать типы аргументов и возвращаемого значения автоматически.

  2. Использование std::declval и decltype: Мы использовали decltype для получения типа, возвращаемого лямбдой, а затем создали объект std::function с полученными типами аргументов и возвращаемого значения.

  3. Примеры вызова: Мы добавили пример использования для каждой сигнатуры, чтобы убедиться, что код работает правильно.

С этими изменениями ваш код должен успешно компилироваться и работать с лямбда-функциями, как вы и ожидаете.

Оцените материал
Добавить комментарий

Капча загружается...