Search code examples
c++c++23

Why does a 'deducing this' template give different results than overloading by rvalue/lvalue?


I wrote this simple test to overload based on whether this is an rvalue or an lvalue. The original version had additional cases, but I reduced it down to the one that's puzzling me.

#include <iostream>

struct A {
  int pp=0;  
  A(int p) : pp(p) {}
  A &prop(int nn) & {pp=nn; return *this;}
  A &&prop(int nn) && {pp=nn; return std::move(*this);}
};

struct B {
  B(const A &aa) {std::cout << "COPY: " << aa.pp << std::endl;}
  B(A &&aa) {std::cout << "MOVE: " << aa.pp << std::endl;}
};

int main() {
  A aa(99);
  B b4(aa.prop(33).prop(4));    // 4 COPY
  std::cout << "4 == " << aa.pp << std::endl;

  return 0;
}

The output is what I expected

COPY: 4
4 == 4

So far, so good. Then I modified A to use a method that deduces this:

struct A {
  int pp=0;  
  A(int p) : pp(p) {}

  template <typename self> A prop(this self &&me, int nn) {
    me.pp=nn;
    return me;
  }
};

I expected to get the same result, but I don't:

MOVE: 4
4 == 33
  1. Why does the second version call the move method?

  2. Even though it calls the move method, I would still expect to see nn set to 4 and not 33.

What's going on?


Solution

  • In the "deduced this" case, you always return an A object by value, so:

    aa.prop(33).prop(4)
    

    assigns 33 to aa.pp, and then the prop(4) part assigns 4 to pp of the copy of aa you just returned, thus leaving aa.pp as 33.

    What you need to do is return an A& or A&& reference, depending on how this gets deduced. To do this, you can use decltype(auto) to deduce the value category of the returned object and return the correct type.