Вопрос по c++, c++11, instantiation, lambda – Создание C ++ лямбда по типу

19

Я хочу способ сделать функтор из функции. Теперь я пытаюсь обернуть вызов функции лямбда-функцией и создать ее позже. Но компилятор говорит, что лямбда-конструктор удален. Так есть ли способ скомпилировать этот код? Или может быть другой способ для этого?

#include <iostream>  

void func()
{
    std::cout << "Hello";
}

auto t = []{ func(); };
typedef decltype(t) functor_type;

template <class F>
void functor_caller()
{
    F f;
    f();
}

int main()
{
    functor_caller<functor_type>();
    return 0;
}

Теперь я получаю такую ошибку компилятора:

error: use of deleted function  '<lambda()>::<lambda>()'

error: a lambda closure type has a deleted default constructor

На мой взгляд, единственный способ - использовать макрос:

#define WRAP_FUNC(f) \
struct f##_functor       \
{                       \
    template <class... Args >                             \
    auto operator()(Args ... args) ->decltype(f(args...)) \
    {                                                     \
        return f(args...);                                \
    }                                                     \
};

затем

WRAP_FUNC(func);

а затем (в основном)

functor_caller<func_functor>()
@ user315052: Это на самом деле та же проблема. Nawaz
Нет, я не хочу его использовать. Я пытаюсь получить тип лямбда и затем построить его по умолчанию. andigor
Я даже не могу объявить переменную типаfunctor_type вmain, Нужно заставить это работать сначала. jxh
Ты пробовалF f(t); ? В настоящее время,functor_caller не знает оt. MSalters
@Andigor: Это не имеет смысла. Для лямбда-типов нет значения по умолчанию. Компилятор говорит вам об этом (нет ctor по умолчанию). MSalters

Ваш Ответ

4   ответа
6

Мы можем предположить, что лямбда всегда будет пустой, поэтому мы можем просто выполнить приведение из другого пустого типа, поскольку они оба имеют одинаковую структуру памяти. Итак, мы создаем класс-оболочку, которая делает объект по умолчанию конструируемой функцией:

template<class F>
struct wrapper
{
    static_assert(std::is_empty<F>(), "Lambdas must be empty");
    template<class... Ts>
    auto operator()(Ts&&... xs) const -> decltype(reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...))
    {
        return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
    }
};

Теперь добавлено статическое утверждение, чтобы лямбда всегда была пустой. Это всегда должно иметь место (поскольку требуется распад на указатель на функцию), но стандарт явно не гарантирует этого. Таким образом, мы используем assert, чтобы хотя бы поймать безумную реализацию лямбд. Затем мы просто приводим класс-обертку к лямбде, поскольку они оба пусты.

Наконец лямбда может быть построена так:

template <class F>
void function_caller()
{
    wrapper<F> f;
    f();
}
Нарушение доступа не гарантируется для разыменования нулевого указателя.
Хорошее решение. Просто небольшая модификация:*reinterpret_cast<F*>(nullptr)(...) будет вызывать нарушение прав доступа во время выполнения всякий раз, когдаF::operator() пытается разыскивать себя вместо того, чтобы молча портить память послеwrapper объект.static_assert() уверяет нас, что «F»; не имеет полей, но ... вы никогда не знаете, чтоF делает.
8

что у вас есть лямбда захвата, подобная этой:

{
    int n = 0;
    auto t = [&n](int a) -> int { return n += a; };
}

Что это может означать для создания объекта по умолчаниюdecltype(t)?

Как подсказывает @Matthieu, вы можете обернуть лямбду вfunction объект:

std::function<int(int)> F = t;

Или вы можете создать шаблон вашего call-сайта непосредственно по типу лямбды (или любой вызываемой сущности):

template <typename F>
int compute(int a, int b, F f)
{
    return a * f(b);  // example
}

Использование:int a = 0; for (int i : { 1, 3, 5 }) { a += compute(10, i, t); }

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

Но мой вопрос был о создании функтора из функции во время компиляции ... andigor
Теоретически построение лямбды по умолчанию в вашем примере означает, что я получаю ссылку наn который может быть ссылкой, если этот объект был разрушен. Но еслиn живет у меня правильная ссылка. andigor
@Andigor: если вы используете стиль шаблона, вам даже не нужно создавать промежуточный функтор - вы можете просто передать функцию (указатель) напрямую ... и заметить, чтоn не обязательно в области видимости и не является значимой частью типа.
8

Лямбды не имеют конструкторов по умолчанию. Когда-либо. Единственные конструкторы, к которым они иногда предоставляют доступ, - это (в зависимости от того, что они захватывают) конструкторы копирования и / или перемещения.

Если вы создадите функтор без общедоступного конструктора по умолчанию, вы получите ту же ошибку.

В C ++ 17 вы можете решить это сconstexpr лямбда иoperator+ распад на указатель на функцию. Тип, который несет указатель на функцию и вызывает ее легко сauto параметры шаблона.

В C ++ 11 ты должен быть немного хакерским.

template<class F>
struct stateless_lambda_t {
  static std::aligned_storage_t< sizeof(F), alignof(F) >& data() {
    static std::aligned_storage_t< sizeof(F), alignof(F) > retval;
    return retval;
  };
  template<class Fin,
    std::enable_if_t< !std::is_same< std::decay_t<Fin>, stateless_lambda_t >{}, int> =0
  >
  stateless_lambda_t( Fin&& f ) {
    new ((void*)&data()) F( std::forward<Fin>(f) );
  }
  stateless_lambda_t(stateless_lambda_t const&)=default;
  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return (*static_cast<F*>( (void*)&data() ))(std::forward<Args>(args)...);
  }
  stateless_lambda_t() = default;
};
template<class F>
stateless_lambda_t<std::decay_t<F>> make_stateless( F&& fin ) {
  return {std::forward<F>(fin)};
}

Теперь мы можем:

auto t = make_stateless([]{ func(); });

и твой код работает.

static_assert или сфина что тоF на самом деле пустой тип может быть хорошей идеей. Вы знаете, для качества.

Использование функций C ++ 14 можно заменить ручнымdecltype и извергатьtypename а также::type ключевые слова. Этот ответ был первоначально написан для вопроса C ++ 14, который был закрыт как дубликат этого.

живой пример.

Это сработало! Спасибо!
Боюсь, я не могу заставить это работать. Не могли бы вы опубликовать полный пример кода?
Исправлены ошибки компиляции @Knarf, опубликован живой пример.
2

Нет.

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

Тем не менее, вместо того, чтобы изобретать велосипед, я бы использовалstd::function вместо.

У вас есть какая-либо четкая информация об этом? andigor
Можете ли вы показать пример использования std :: function? andigor
Нет. Пример использования std :: function в контексте моего вопроса. andigor
Спасибо! Я думал, что компилятор генерирует простой функтор, когда увидел лямбда-объявление и тело лямбды находится в перегруженномoperator() Кажется, я был не прав .... andigor
@Andigor: я наконец понял проблему.body лямбда не является частью его типа. Поэтому построение лямбды по типу по умолчанию, даже если бы это было возможно, не имело бы смысла. Это похоже на конструкцию по умолчаниюvoid (*)()все, что вы получаете - это нулевой указатель на гипотетическую функцию, которая не принимает параметров и ничего не возвращает = & gt; нет ничего значимого, что вы можете сделать с этим нулевым указателем! Вот почему вам нужноcopy существующая лямбда.

Похожие вопросы