Передача лямбд в качестве параметров шаблона: какой тип на самом деле выводится? - PullRequest
1 голос
/ 04 апреля 2019

Если я передаю лямбду в качестве параметра шаблона, каков фактический тип этого параметра, который выводится?Я посмотрел на отладчик VS2017 и тип этой лямбда: [](int x) {return x; } это filename::__I2::int<lambda>(int).

Причина, по которой я спрашиваю это, состоит в том, что я хочу передать лямбду, а затем создать внутреннюю std::function из этого.Обратите внимание, что это относится к этому ответу и почему мы должны использовать CTAD для создания внутреннего std::function вместо простой передачи параметра шаблона в std::function.

В качестве примера я хотел бы сделать что-то вроде следующего:

template<class Func, class... Args> 
void createStdFunc(Func f, Args... args) {
    std::function<Func> internalFunc = f; //this does not work
}

//usage
createStdFunc([](int x) {return x; }, 5);

Однако это не работает, и я получаю ошибку 'initialising' cannot convert from 'Func' to 'std::function<Func>'.Я не уверен, как типы отличаются и как они изменились от перехода в функцию до инициализации std::function.Обратите внимание, что я знаю, что вы можете использовать CTAD начиная с 2017 года, но вам было интересно, каким было бы решение на 2014 год и ранее?

Ответы [ 5 ]

3 голосов
/ 04 апреля 2019

В C ++ 14 вы можете использовать вывод типа возврата, чтобы выяснить сигнатуру функции, это означает, что типы аргументов, передаваемые в createStdFunc match:

template<class Func, class... Args> 
void createStdFunc(Func f, Args... args) {
    std::function<std::result_of_t<Func(Args...)> (Args...)> internalFunc{f}; //this does work
}
2 голосов
/ 04 апреля 2019

Мой путь

#include <iostream>
#include <functional>

template <typename R, typename T, typename ... As>
constexpr std::function<R(As...)> getFuncType (R(T::*)(As...) const);

template <typename F, typename ... As>
void createStdFunc (F const & f, As ... as)
 {
   decltype(getFuncType(&F::operator()))  internalFunc { f };

   internalFunc(as...);
 }

int main ()
 {
   createStdFunc([](int x) { std::cout << x << std::endl; }, 5);
 }

Возможно также через using

template <typename F>
using funcType = decltype(getFuncType(&F::operator()));

template <typename F, typename ... As>
void createStdFunc (F const & f, As ... as)
 {
   funcType<F> internalFunc { f };

   internalFunc(as...);
 }
1 голос
/ 04 апреля 2019

Шаблон std::function ожидает в качестве аргумента тип функции, из которого он выводит тип возвращаемого значения и параметра для вызываемой обертки. Тип закрытия лямбда-выражения является вызываемым, но это не тип функции.

В C ++ 17 введены инструкции по выводу для std::function, которые позволяют определить правильный тип из любого вызываемого аргумента. До C ++ 17 вы могли использовать набор вспомогательных шаблонов для определения правильного типа, например:

template <typename F>
struct deduce_func_type_helper;

template <typename R, typename... Args>
struct deduce_func_type_helper<R(&)(Args...)>
{
    using type = std::function<R(Args...)>;
};

template <typename R, typename... Args>
struct deduce_func_type_helper<R(*)(Args...)> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename C, typename R, typename... Args>
struct deduce_func_type_helper<R(C::*)(Args...)> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename C, typename R, typename... Args>
struct deduce_func_type_helper<R(C::*)(Args...) const> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename C, typename R, typename... Args>
struct deduce_func_type_helper<R(C::*)(Args...) volatile> : deduce_func_type_helper<R(&)(Args...)> {};

template <typename F>
struct deduce_func_type_helper<F&> : deduce_func_type_helper<std::remove_cv_t<F>> {};

template <typename F>
struct deduce_func_type_helper<F&&> : deduce_func_type_helper<std::remove_cv_t<F>> {};

template <typename F>
struct deduce_func_type_helper : deduce_func_type_helper<decltype(&F::operator())> {};

template <typename F>
using func_type_t = typename deduce_func_type_helper<F>::type;

живой пример здесь

Обратите внимание, что приведенный выше пример не завершен; в нем отсутствуют некоторые специализации, например, для всех возможных комбинаций const, volatile и различных квалификаторов ссылок. Так что это может быть довольно многословно, вам, вероятно, захочется использовать C ++ 17, если вы можете & hellip;

1 голос
/ 04 апреля 2019

Вы можете написать простую черту для обобщения вызываемых типов. Если вы обрабатываете оба указателя функций и что-либо еще с operator() (как const, так и не const), вы сможете охватить большинство случаев использования.

#include <tuple>

// For callable types
template<class T>
struct func_type : func_type<decltype(&T::operator())>{};

// For callable types' member functions (including `operator()`)
template<class T, class R, class ... Args >
struct func_type<R (T::*)(Args...) const> : func_type<R(*)(Args...)> {};

// For function pointers
template<class R, class ... Args >
struct func_type<R (*)(Args...)> {
    using type = R(Args...);
    using result = R;
    using args = std::tuple<Args...>;
};

template<class T>
using func_type_t = typename func_type<T>::type;

func_type_t<T> должен затем дать вам тип функции для большинства вызываемых типов T. Пример использования:

#include <functional>

template<class Func, class... Args>
void createStdFunc(Func f, Args... args) {
    // Replaced `Func` with `func_type_t<Func>`
    std::function<func_type_t<Func>> internalFunc = f;
}

int foo(int x) { return x; }

struct bar {
    int operator()(int x) { return x; };
};


int main()
{
    // With lambda expression
    createStdFunc([](int x) {return x; }, 5);

    // With function pointer
    createStdFunc(foo, 5);

    // With std::function
    std::function<int(int)> std_func = [](int x) {return x; };
    createStdFunc(std_func, 5);

    // With a functor
    createStdFunc(bar{}, 5);
}
1 голос
/ 04 апреля 2019

Проблема в вашем коде в том, что Func не является типом функции.Это тип лямбды.Lambdas компилируется примерно так:

// equivalent:
// auto my_lambda = [](int v){ return v; };

struct /* unnamed */ {
    auto operator()(int v) const { return v; }
} my_lambda;

Решение будет состоять в том, чтобы извлечь тип operator() из типа замыкания:

using my_lambda_t = decltype(my_lambda);

// type: int(my_lambda_t::*)(int) const; 
auto call_operator = &decltype(my_lambda_t)::operator();

Затем из типаoperator(), вы можете определить тип аргументов и тип возвращаемого значения:

template<typename>
struct extract_types {};

template<typename R, typename C, typename... Args>
struct extract_types<R(C::*)(Args...) const> {
    using result = R;
    using args_types = std::tuple<Args...>;
};

Обобщенные версии этого шаблона поставляются в Boost.CallableTraits

...