Подтвердить что ты не робот

Захват std:: prom в lambda С++ 14

Я хочу создать конечный автомат, который обрабатывает переданные сигналы в своем потоке. Я использую Visual Studio 2015, поэтому поддерживается С++ 11 и частично С++ 14. Сигналы хранятся в контейнерах. Каждый сигнал представлен как функция std::. Я хотел бы подождать от клиента до тех пор, пока машина состояния не обработает сигнал, который был отправлен, поэтому это своего рода синхронный сигнал.

Моя проблема: я не могу зафиксировать std:: prom в лямбда и добавить ее в контейнер.

#include <functional>
#include <future>
#include <list>

std::list<std::function<int()>> callbacks;

void addToCallbacks(std::function<int()>&& callback)
{
    callbacks.push_back(std::move(callback));
}

int main()
{
    std::promise<int> prom;
    auto fut = prom.get_future();

    // I have made the lambda mutable, so that the promise is not const, so that I can call the set_value
    auto callback = [proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; };

    // This does not compile
    addToCallbacks(std::move(callback));

    // This does not compile either, however this lambda is a temporal value (lvalue)
    addToCallbacks([proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; });

    return 0;
}

Каковы решения, если

  • Я хочу избежать захвата обещания ссылкой
  • Я хочу избежать захвата указателя * или shared_ptr для обещания

Было бы неплохо вложить обещание в класс так или иначе, как генерируется лямбда. Это означает, что лямбда больше не копируется, а только подвижная. Возможно ли вообще?

4b9b3361

Ответ 1

std::function может быть создан только из функторов, которые можно копировать. Из [func.wrap.func.con]:

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

Требуется: F должен быть CopyConstructible.

std::promise не копируется, поэтому нет возможности привязать функтор с этим членом к std::function. Период.

Учитывая, что вы хотите, чтобы ваш функтор фактически взял на себя ответственность за обещание, это не оставляет вам много вариантов. В значительной степени std::shared_ptr<std::promise>. Любая другая опция либо не работает (например, std::unique_ptr<std::promise>), оставляет вас с оборванным объектом (например, std::reference_wrapper<std::promise>), либо оставляет проблемы с управлением памятью (например, std::promise*).


Однако вы могли бы использовать что-то другое, кроме std::function. Здесь вы можете взглянуть на идею здесь, а также dyp function_mo здесь, оба из которых создают подвижные ароматы std::function.

Ответ 2

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

#include <iostream>
#include <functional>
#include <future>
#include <list>

// declare a non-polymorphic container for any function object that takes zero args and returns an int
// in addition, the contained function need not be copyable
class move_only_function
{
    // define the concept of being callable while mutable
    struct concept
    {
        concept() = default;
        concept(concept&&) = default;
        concept& operator=(concept&&) = default;
        concept(const concept&) = delete;
        concept& operator=(const concept&) = default;
        virtual ~concept() = default;

        virtual int call() = 0;
    };

    // model the concept for any given function object
    template<class F>
    struct model : concept
    {
        model(F&& f)
        : _f(std::move(f))
        {}

        int call() override
        {
            return _f();
        }

        F _f;
    };

public:
    // provide a public interface
    int operator()()  // note: not const
    {
        return _ptr->call();
    }

    // provide a constructor taking any appropriate object
    template<class FI>
    move_only_function(FI&& f)
    : _ptr(std::make_unique<model<FI>>(std::move(f)))
    {}

private:
    std::unique_ptr<concept> _ptr;
};

std::list<move_only_function> callbacks;

void addToCallbacks(move_only_function&& callback)
{
    callbacks.push_back(std::move(callback));
}

int main()
{
    std::promise<int> prom;
    auto fut = prom.get_future();

    // I have made the lambda mutable, so that the promise is not const, so that I can call the set_value
    auto callback = [proms=std::move(prom)]() mutable { proms.set_value(5); return 5; };

    // This now compiles
    addToCallbacks(std::move(callback));

    std::promise<int> prom2;
    auto fut2 = prom2.get_future();

    // this also compiles
    addToCallbacks([proms = std::move(prom2)]() mutable { proms.set_value(6); return 6; });

    for (auto& f : callbacks)
    {
        std::cout << "call returns " << f() << std::endl;
    }

    std::cout << "fut = " << fut.get() << std::endl;
    std::cout << "fut2 = " << fut2.get() << std::endl;

    return 0;
}

ожидаемый вывод:

call returns 5
call returns 6
fut = 5
fut2 = 6

Ответ 3

Другой простой подход может заключаться в использовании идиомы деструктивного копирования и переноса типа "только подвижный" в тривиальную структуру CopyConstructible :

#include <functional>
#include <future>
#include <type_traits>

template <typename T>
struct destructive_copy_constructible
{
    mutable T value;

    destructive_copy_constructible() {}

    destructive_copy_constructible(T&& v): value(std::move(v)) {}

    destructive_copy_constructible(const destructive_copy_constructible<T>& rhs)
        : value(std::move(rhs.value))
    {}

    destructive_copy_constructible(destructive_copy_constructible<T>&& rhs) = default;

    destructive_copy_constructible&
    operator=(const destructive_copy_constructible<T>& rhs) = delete;

    destructive_copy_constructible&
    operator=(destructive_copy_constructible<T>&& rhs) = delete;
};

template <typename T>    
using dcc_t = 
    destructive_copy_constructible<typename std::remove_reference<T>::type>;

template <typename T>
inline dcc_t<T> move_to_dcc(T&& r)
{
    return dcc_t<T>(std::move(r));
}

int main()
{
    std::promise<int> result;

    std::function<void()> f = [r = move_to_dcc(result)]()
    {
        r.value.set_value(42);
    };

    return 0;
}

Несмотря на то, что идиома разрушительного копирования считается опасной и устарела из-за идиомы перемещения, она все еще может быть полезной, по крайней мере, для того, чтобы закрыть такую дыру std::function.

Преимущество в этом случае - отсутствие накладных расходов (без копирования/динамического выделения памяти) по сравнению с предлагаемыми решениями std::shared_ptr и move_only_function. А опасность неправильного использования скопированного исходного объекта в основном снижается за счет использования подобного движению синтаксиса, который четко описывает разрушительную семантику копирования/перемещения.

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