Может ли std:: function быть перемещена по ходу из ссылки rvalue на временный объект-функтор?
У меня есть неисследованный объект-функтор, который я пытаюсь сохранить как std::function
внутри другого объекта. Этот объект действительно тяжеловесный, поэтому он помечается как незамкнутый, но имеет конструктор перемещения. Однако попытка построить std:: function или назначить его из временного конструктора не удалась.
Вот минимальный пример, чтобы спровоцировать ошибку.
// pretend this is a really heavyweight functor that can't be copied.
struct ExampleTest
{
int x;
int operator()(void) const {return x*2;}
ExampleTest( ) :x(0){}
ExampleTest( int a ) :x(a){}
// allow move
ExampleTest( ExampleTest &&other ) :x(other.x) {};
private: // disallow copy, assignment
ExampleTest( const ExampleTest &other );
void operator=( const ExampleTest &other );
};
// this sometimes stores really big functors and other times stores tiny lambdas.
struct ExampleContainer
{
ExampleContainer( int );
std::function<int(void)> funct;
};
/******** ERROR:
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member
declared in class 'ExampleTest'
******************/
ExampleContainer::ExampleContainer( int x )
: funct( ExampleTest( x ) )
{}
/******** ERROR:
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member
declared in class 'ExampleTest'
******************/
int SetExample( ExampleContainer *container )
{
container->funct = ExampleTest();
return container->funct();
}
В еще более простой конструкции, где я просто создаю локальную функцию, я также получаю ошибку:
int ContrivedExample( )
{
// extra parens to sidestep most vexing parse
std::function<int()> zug( (ExampleTest()) );
/*** ERROR: 'ExampleTest::ExampleTest' : cannot access private member
declared in class 'ExampleTest' */
int troz = zug( ) ;
return troz;
}
Насколько я могу судить, во всех этих случаях временный ExampleTest должен быть передан конструктору функции как rvalue. Однако компилятор хочет их скопировать.
Что дает? Возможно ли передать объекты-конструкторы (но с возможностью перемещения) в конструктор std:: function? Есть обходные пути с указателями и так далее, но я хочу понять, что здесь происходит.
Конкретные ошибки, приведенные выше, относятся к Visual Studio 2012 с патчем CTP С++ 11. GCC 4.8 и Clang 3 также падают с собственными сообщениями об ошибках.
Ответы
Ответ 1
Этот объект действительно тяжеловесный, поэтому он помечен как непокрытый, но имеет конструктор перемещения.
Если функтор не копируется, он не отвечает необходимым требованиям для использования с std::function
. Параграф 20.8.11.2.1/7 стандарта С++ 11 указывает:
template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
7 Требуется: F
должно быть CopyConstructible
. F
должен быть Callable
(20.8.11.2) для типов аргументов ArgTypes
и тип возврата R
. Конструктор копирования и деструктор A
не должны генерировать исключения.
Ответ 2
Функция std :: function может быть построена по движению из r-значения объекта-функтора. И большинство реализаций делают это.
Требование "моя цель должно быть готово к копированию" для функции std :: обусловлено собственным требованием о возможности копирования. Тип std :: function определяется только его целевой сигнатурой (например: void (int)), а сама std :: function определяется стандартом, который должен быть доступен для копирования. Поэтому, когда вы копируете конструкцию std :: function, ей нужно вызвать copy-ctor своей цели (базовый функтор). Таким образом, для этого требуется, чтобы его цель была одна. У него нет другого выбора.
Имея требование о том, чтобы цель была построена для копирования, стандарт не говорит, что реализации должны копировать вместо перемещения, когда вы создаете std :: функцию из объекта, вызываемого rvalue. Реализация, вероятно, вызовет только вызов move-ctor вашего вызываемого объекта.
Более подробная дополнительная информация с примерами и тестами:
Например, в gcc (MSVC аналогичная) реализация для ctor std :: function из любого вызываемого объекта:
template<typename _Res, typename... _ArgTypes>
template<typename _Functor, typename>
function<_Res(_ArgTypes...)>::
function(_Functor __f)
: _Function_base()
{
typedef _Function_handler<_Signature_type, _Functor> _My_handler;
// don't need to care about details below, but when it uses __f, it
// either uses std::move, or passes it by references
if (_My_handler::_M_not_empty_function(__f))
{
_My_handler::_M_init_functor(_M_functor, std::move(__f));
_M_invoker = &_My_handler::_M_invoke;
_M_manager = &_My_handler::_M_manager;
}
}
передача по значению аргумента "_Functor __f" будет использовать его конструктор перемещения, если он есть, и он будет использовать свой конструктор копирования, если он не имеет перемещения ctor. Как показывает следующая тестовая программа:
int main(){
using namespace std;
struct TFunctor
{
TFunctor() = default;
TFunctor(const TFunctor&) { cout << "cp ctor called" << endl; }
TFunctor(TFunctor&&) { cout << "mv ctor called" << endl; };
void operator()(){}
};
{ //!!!!COPY CTOR of TFunctor is NEVER called in this scope
TFunctor myFunctor;
//TFunctor move ctor called here
function<void()> myStdFuncTemp{ std::move(myFunctor) };
function<void()> myStdFunc{ move(myStdFuncTemp) };
}
{ //TFunctor copy ctor is called twice in this scope
TFunctor myFunctor;
//TFunctor copy ctor called once here
function<void()> myStdFuncTemp{ myFunctor };
//TFunctor copy ctor called once here
function<void()> myStdFunc{ myStdFuncTemp };
}
}
Наконец, вы можете создать und :: function_only_movable, который имеет почти все то же самое с std :: function, но удаляет свою собственную копию ctor, поэтому нет необходимости требовать, чтобы целевой вызываемый объект имел один экземпляр ctor. Вам также нужно только построить его из rvalue вызываемых объектов.