Передача объекта, не подлежащего копированию, в std:: function parameter
В С++ 14 выражение лямбда может захватывать переменные, перемещаясь от них с помощью инициализаторов захвата. Однако это приводит к тому, что результирующий объект закрытия не копируется. Если у меня есть существующая функция, которая принимает аргумент std::function
(который я не могу изменить), я не могу передать объект закрытия, потому что конструктор std::function
требует, чтобы данный функтор был CopyConstructible
.
#include <iostream>
#include <memory>
void doit(std::function<void()> f) {
f();
}
int main()
{
std::unique_ptr<int> p(new int(5));
doit([p = std::move(p)] () { std::cout << *p << std::endl; });
}
Это приводит к следующим ошибкам:
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1911:10: error:
call to implicitly-deleted copy constructor of '<lambda at test.cpp:10:7>'
new _Functor(*__source._M_access<_Functor*>());
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: note: in
instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
>::_M_clone' requested here
_M_clone(__dest, __source, _Local_storage());
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: note: in
instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
>::_M_manager' requested here
_M_manager = &_My_handler::_M_manager;
^
test.cpp:10:7: note: in instantiation of function template specialization 'std::function<void
()>::function<<lambda at test.cpp:10:7>, void>' requested here
doit([p = std::move(p)] () { std::cout << *p << std::endl; });
^
test.cpp:10:8: note: copy constructor of '' is implicitly deleted because field '' has a deleted
copy constructor
doit([p = std::move(p)] () { std::cout << *p << std::endl; });
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/unique_ptr.h:273:7: note:
'unique_ptr' has been explicitly marked deleted here
unique_ptr(const unique_ptr&) = delete;
^
Есть ли разумное решение?
Тестирование с помощью Uanguntu clang версии 3.5-1 ~ exp1 (trunk)
Ответы
Ответ 1
Существует такой подход:
template< typename signature >
struct make_copyable_function_helper;
template< typename R, typename... Args >
struct make_copyable_function_helper<R(Args...)> {
template<typename input>
std::function<R(Args...)> operator()( input&& i ) const {
auto ptr = std::make_shared< typename std::decay<input>::type >( std::forward<input>(i) );
return [ptr]( Args... args )->R {
return (*ptr)(std::forward<Args>(args)...);
};
}
};
template< typename signature, typename input >
std::function<signature> make_copyable_function( input && i ) {
return make_copyable_function_helper<signature>()( std::forward<input>(i) );
}
где мы делаем общий указатель на наши данные, затем создаем скопированную лямбду, которая захватывает этот общий указатель, затем мы переносим эту скопированную лямбда в std::function
запрошенной сигнатуры.
В вашем случае выше вы просто:
doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );
Немного более продвинутая версия отбрасывает стирание типа и добавляет слой совершенной пересылки для уменьшения накладных расходов:
template<typename input>
struct copyable_function {
typedef typename std::decay<input>::type stored_input;
template<typename... Args>
auto operator()( Args&&... args )->
decltype( std::declval<input&>()(std::forward<Args>(args)...) )
{
return (*ptr)(std::forward<Args>(args));
}
copyable_function( input&& i ):ptr( std::make_shared<stored_input>( std::forward<input>(i) ) ) {}
copyable_function( copyable_function const& ) = default;
private:
std::shared_ptr<stored_input> ptr;
};
template<typename input>
copyable_function<input> make_copyable_function( input&& i ) {
return {std::forward<input>(i)};
}
который не требует от вас пропускать подпись и может быть несколько более эффективным в нескольких случаях, но использует более неясные методы.
В С++ 14 с этим можно сделать еще более кратким:
template< class F >
auto make_copyable_function( F&& f ) {
using dF=std::decay_t<F>;
auto spf = std::make_shared<dF>( std::forward<F>(f) );
return [spf](auto&&... args)->decltype(auto) {
return (*spf)( decltype(args)(args)... );
};
}
полностью избавиться от необходимости использовать вспомогательный тип.
Ответ 2
Если время жизни объекта замыкания не является проблемой, вы можете передать его в ссылочной оболочке:
int main()
{
std::unique_ptr<int> p(new int(5));
auto f = [p = std::move(p)]{
std::cout << *p << std::endl;
};
doit(std::cref(f));
}
Это явно не относится ко всем сценариям, но отлично подходит для вашей примерной программы.
EDIT: взглянув на N3797 (рабочий проект С++ 14) § 20.9.11.2.1 [func.wrap.func.con] p7, требование CopyConstructible
по-прежнему существует. Интересно, есть ли техническая причина, которая не может быть ослаблена до MoveConstructible
, или если комитет просто не обошел ее?
EDIT: Отвечая на мой собственный вопрос: std::function
есть CopyConstructible
, поэтому завершенный функтор должен быть CopyConstructible
.
Ответ 3
Если вы знаете, что на самом деле не собираетесь копировать свой функциональный объект, вы можете просто обернуть его в тип, который заставит компилятор думать, что он копируемый:
struct ThrowOnCopy {
ThrowOnCopy() = default;
ThrowOnCopy(const ThrowOnCopy&) { throw std::logic_error("Oops!"); }
ThrowOnCopy(ThrowOnCopy&&) = default;
ThrowOnCopy& operator=(ThrowOnCopy&&) = default;
};
template<typename T>
struct FakeCopyable : ThrowOnCopy
{
FakeCopyable(T&& t) : target(std::forward<T>(t)) { }
FakeCopyable(FakeCopyable&&) = default;
FakeCopyable(const FakeCopyable& other)
: ThrowOnCopy(other), // this will throw
target(std::move(const_cast<T&>(other.target))) // never reached
{ }
template<typename... Args>
auto operator()(Args&&... a)
{ return target(std::forward<Args>(a)...); }
T target;
};
template<typename T>
FakeCopyable<T>
fake_copyable(T&& t)
{ return { std::forward<T>(t) }; }
// ...
doit( fake_copyable([p = std::move(p)] () { std::cout << *p << std::endl; }) );
Шаблон функции fake_copyable
создает оболочку, которая является CopyConstructible
согласно компилятору (и <type_traits>
), но не может быть скопирована во время выполнения.
Если вы сохраните FakeCopyable<X>
в std::function
и затем в конечном итоге std::logic_error
std::function
вы получите брошенный std::logic_error
, но если вы только переместите std::function
все будет работать нормально.
target(std::move(const_cast<T&>(other.target)))
выглядит тревожно, но этот инициализатор никогда не запустится, потому что инициализатор базового класса бросит первым. Так что беспокойство const_cast
самом деле никогда не происходит, оно просто радует компилятор.