how to effectively turn rvalue reference to lvalue in perfect forwarding

I am implementing a simple generic functor class which can store a function as well as its arguments, and thus it can be called later. The function can be a free function or member function of a class, which takes arbitrary number of arguments. So I use perfect forwording.

Here is the class:

template< typename FUNC, typename...ARGS>
class GenericFunctor{

    using ReturnType = std::invoke_result_t<FUNC, ARGS...>;

    std::function< ReturnType() > functor;

public:

    GenericFunctor( FUNC&& func, ARGS &&... args ){
        functor = std::bind( std::forward<FUNC>(func), std::forward<ARGS>(args)... );
    }

    void run(){
        functor();
    }

};

I test it with this simple class:

class Person{

    int age;
    double height;

public:

    Person( int age, double height):age(age), height( height){}

    void print(){
        cout << age << " " << height << "\n";
    }

    int getAge(){
        return age;
    }
};

I expect the following 3 case can work:

GenericFunctor f_1( &Person::getAge, std::ref(p1) ) );
f_1.run();

GenericFunctor f_2( &Person::getAge, std::move(p1) );
f_2.run();

GenericFunctor f_3( &Person::getAge, p1 );
f_3.run(); 

But in the case of f_3, I got this error:

error: cannot bind rvalue reference of type 'Person&&' to lvalue of type 'Person'

I know what the above message means, but I have no idea how to modify my class, GenericFunctor, to make it generic enough so that all of the above 3 cases can work just as std::thread. Could I have some guidance of how to modify my GenericFunctor?

>Solution :

CTAD (class template argument deduction) doesn’t respect forwarding references (treats them as rvalue references, only accepting rvalues), unless you add a custom deduction guide.

Add one after your class:

template <typename FUNC, typename ...ARGS>
GenericFunctor(FUNC &&, ARGS &&...) -> GenericFunctor<FUNC, ARGS...>;

But the whole class doesn’t really need to have FUNC and ARGS... as template parameters. Only the constructor needs them.

The whole class only needs the return type. You can write a deduction guide to avoid having to write it manually:

template <typename R>
class GenericFunctor
{
    std::function<R()> functor;

    // ...

    template <typename FUNC, typename ...ARGS>
    GenericFunctor(FUNC &&func, ARGS &&... args) {/*...*/}
};

template <typename FUNC, typename ...ARGS>
GenericFunctor(FUNC &&, ARGS &&...) -> GenericFunctor<std::invoke_result_t<FUNC &&, ARGS &&...>>;

Leave a Reply