如何在成员函数中完美转发`*this`对象

How to perfectly forward `*this` object inside member function

是否可以在成员函数中完美转发*this对象?如果是,那我们该怎么做呢?如果不是,那为什么不呢,我们有什么替代方法可以达到同样的效果。

请查看下面的代码片段以更好地理解问题。

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
}

如果我们认为成员函数 double sum() 中的 *this 对象始终是左值或 xvalue(因此是泛左值),则此输出似乎是预期的。请确认这是否属实。

如何才能完美转发*this对象到double sum()成员函数内部的成员函数调用someConstant()

我尝试使用 std::forward 如下:

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

但这没有任何效果,double someConstant() & 重载总是被调用。

如果不为 &&& 限定符重载 sum,这在 C++11 中是不可能的。 (在这种情况下,您可以根据特定重载的限定符确定值类别。)

*this 就像任何间接的结果一样,是一个左值,也是调用隐式成员函数调用的对象。

这将在 C++23 中通过引入可以应用常规转发的显式对象参数来解决:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html

人们会认为 std::forward() 会保留左值引用,但它不会在 non-template 上下文中,如下例所示。

call_f()&call_f()&& 都调用了 f()&&std::forward<Experiment>(*this) 在 non-template 函数中 returns 右值引用,与参数的值类别无关。

请注意这与模板函数有何不同,无论是否为成员(我将成员函数设为静态,因为它接收“this 引用”作为显式参数)。两个都“正​​确地”转发左值引用(最后 4 次调用)。

#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';
}

在结果输出中,第三行令人惊讶:std::forward<Experiment>(*this) 的类型 *this 的左值引用是右值引用。

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()&&