Search code examples
c++template-meta-programming

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 my code:

#include <iostream>
#include <string>
#include <functional>

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();
    }
};

class Person{

    int age;
    double height;

public:

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

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

    int getAge(){
        return age;
    }
};


int main() {

    Person p1( 32, 183.2);

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

    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();
}

I expect the above 4 test cases can work. 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 &&...>>;