Home > Software engineering >  How to perfectly forward `*this` object inside member function
How to perfectly forward `*this` object inside member function

Time:03-05

Is it possible to perfectly forward *this object inside member functions? If yes, then how can we do it? If no, then why not, and what alternatives do we have to achieve the same effect.

Please see the code snippet below to understand the question better.

class Experiment {
public:
  double i, j;
  Experiment(double p_i = 0, double p_j = 0) : i(p_i), j(p_j) {}

  double sum() { return i   j   someConstant(); }

  double someConstant() && { return 10; }

  double someConstant() & { return 100; }
};

int main() {
  Experiment E(3, 5);
  std::cout << std::move(E).sum() << "\n";  // prints: 108
  std::cout << E.sum() << "\n";             // prints: 108
}

This output seems expected if we consider that *this object inside the member function double sum() is always either an lvalue or xvalue (thus a glvalue) . Please confirm if this is true or not.

How can we perfectly forward *this object to the member function call someConstant() inside the double sum() member function?

I tried using std::forward as follows:

double sum() {
    return i   j   std::forward<decltype(*this)>(*this).someConstant();
}

But this did not have any effect, and double someConstant() & overload is the one always being called.

CodePudding user response:

This is not possible in C 11 without overloading sum for & and && qualifiers. (In which case you can determine the value category from the qualifier of the particular overload.)

*this is, just like the result of any indirection, a lvalue, and is also what an implicit member function call is called on.

This will be fixed in C 23 via introduction of an explicit object parameter for which usual forwarding can be applied: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html

CodePudding user response:

One would think that std::forward() would preserve lvalue references but they don't in non-template contexts, as the example below shows.

Both call_f()& and call_f()&& call f()&&. std::forward<Experiment>(*this) in a non-template function returns an rvalue reference regardless of the value category of the argument.

Note how this works differently from a template function, member or not (I made the member function static because it receives a "this reference" as an explicit parameter) . Both forward lvalue references "properly" (the last 4 calls).

#include<iostream>
#include<utility>
#include<string>

struct Experiment 
{
public:
    std::string f()&& { return "f()&&"; }
    std::string f()&  { return "f()&"; }

    std::string call_f()&& { std::cout << "call_f()&& "; return std::forward<Experiment>(*this).f(); }

    // I need this function because it is not a template function
    std::string call_f()& { std::cout << "call_f()& "; return std::forward<Experiment>(*this).f(); }

    template<class T = Experiment>
    static std::string E_t_call_f(T&& t) { std::cout << "E_t_call_f(T&& t) "; return std::forward<T>(t).f(); }
};

template<class T> 
std::string t_call_f(T&& t) { std::cout << "t_call_f(T&& t) "; return std::forward<T>(t).f(); }

int main() 
{
    Experiment E;
    std::cout << "E.f(): " << E.f() << '\n';
    std::cout << "move(E).f(): " << std::move(E).f() << '\n';
    std::cout << '\n';
    std::cout << "E.call_f(): " << E.call_f() << '\n';
    std::cout << "move(E).call_f(): " << std::move(E).call_f() << '\n';
    std::cout << '\n';
    std::cout << "t_call_f(E): " << t_call_f(E) << '\n';
    std::cout << "t_call_f(std::move(E)): " << t_call_f(std::move(E)) << '\n';
    std::cout << '\n';
    std::cout << "E::E_t_call_f(E): " << Experiment::E_t_call_f(E) << '\n';
    std::cout << "E::E_t_call_f(std::move(E)): " << Experiment::E_t_call_f(std::move(E)) << '\n';
}

In the resulting output it is the third line that's surprising: The type of std::forward<Experiment>(*this) for an lvalue reference to *this is an rvalue reference.

E.f(): f()&
move(E).f(): f()&&

call_f()& E.call_f(): f()&&
call_f()&& move(E).call_f(): f()&&

t_call_f(T&& t) t_call_f(E): f()&
t_call_f(T&& t) t_call_f(std::move(E)): f()&&

E_t_call_f(T&& t) E::E_t_call_f(E): f()&
E_t_call_f(T&& t) E::E_t_call_f(std::move(E)): f()&&
  • Related