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

Может ли 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 также падают с собственными сообщениями об ошибках.

4b9b3361

Ответ 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 вызываемых объектов.