4

I want to build a structure that allow me to call member functions with an undefined number of parameters. For now I wrote something like this

template<typename Class, typename Return, typename ... Args>
struct Caller
{
private:
    std::function<Return(Args ...)> callerFunction;

    Caller() = delete;
    Caller(const Caller&) = delete;
    Caller(Caller&&) = delete;
    Caller& operator=(const Caller&) = delete;

public:
    ~Caller() = default;

    Caller(Class& instance, Return(Class::*function)(Args ...))
    {
        callerFunction = [&instance, function](Args... args)
        {
            return (instance.*function)(args ...);
        };
    }

    Return operator() (Args ... args)
    {
        return callerFunction(args ...);
    }
};

I am afraid that I cannot work around the fact that I cannot declare a std::function variable as std::function<Return<Args&& ...)> callerFunction

When I try to do this the compiler says that it cannot convert from int to int&& (if for example the parameters are ints), so I'm guessing that the function sees the Args&& ... as a parameter pack of rvalue references. Am I correct?

Is there a workaround?

Edit: Ok, I was declaring the function inside the Caller constructor in the wrong way.

Wrong way --> Caller(Class& instance, Return(Class::*function)(Args&& ...))

Right way --> Caller(Class& instance, Return(Class::*function)(Args ...))

The (I guess) right implementation is

template<typename Class, typename Return, typename ... Args>
struct Caller
{
private:
    std::function<Return(Args&& ...)> callerFunction;

    Caller() = delete;
    Caller(const Caller&) = delete;
    Caller(Caller&&) = delete;
    Caller& operator=(const Caller&) = delete;

public:
    ~Caller() = default;

    Caller(Class& instance, Return(Class::*function)(Args ...))
    {
        callerFunction = [&instance, function] (Args&&... args)
        {
            return (instance.*function)(std::forward<Args>(args) ...);
        };
    }

    Return operator() (Args&& ... args)
    {
        return callerFunction(std::forward<Args>(args) ...);
    }
};

Now the question is: Why do I need to declare the function anyway without the double &?

Astinog
  • 1,151
  • 3
  • 12
  • 35

2 Answers2

1

When you define your class template like this:

template <typename T>
struct A {
 A(T&& param)
}

And then create an instace:

A<int> myInstance(someIntVariable);

It won't compile. The reason is, the type of T is explicitly specified by you (as an int, in A<int>), and your class constructor parameter is no longer T&&, but int&&, so it's no longer universal reference (which accepts both lvalue and rvalue references), but regular rvalue reference.

Next, if you pass it some integer, there is a type missmatch error because you pass a regular variable when rvalue reference is expected.

In your example you explicitly defined function signatures, so the same applies - constructor expects a function taking rvalue references to Args..., but that's not true.

I think it's better explained in this question

Outshined
  • 709
  • 7
  • 22
  • Thank you for the explanation, it is so obvious now! My only doubt is: I DON'T need to use forward in any case because I'm specifying the types of every variable that I will pass to the function, right? I don't need to use forward even in the operator() function, that I use to call the member function with the arguments, right? – Astinog Sep 29 '17 at 15:00
  • 1
    @Astinog That's right. std::forward should be used only with universal references. It's sole purpose is to pass the parameter (forward) to another function without altering it's type, so if the T&& will be passed a lvalue, the lvalue will be passed, if rvalue then rvalue. You can read more about it if you google the "perfect forwarding in C++", there are lot's of resources on the subject :) – Outshined Sep 29 '17 at 15:08
0

int&& is not the same as int. If your method takes an int, it doesn't take an int&&. They are different types.

There is no conversion between different signatures of member function pointer.

Forwarding references aren't magic. They depend on deduction rules (and no use of Args&&... above is in a deduced context) and on reference collapsing rules.

As such, your

Return operator() (Args&& ... args)

is also wrong. If Args... is int, then you won't be able to call the above with int a; blah.operator()(a) as a won't bind to int&&.

Honestly, your entire type should be thrown out and replaced with a std::function<Return(Args...)>. Class is a useless narrowing of what the function does, and your wrappers don't add much either.

Users can just [&a](auto&&...args)->decltype(auto){ return a.foo(decltype(args)(args)...); } if they really need to replicate your constructor, or use std::bind( &A::foo, std::ref(a) )

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524