Should c++23's "deducting this" feature be used with template function?

184 Views Asked by At

I was reading https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/ specifically this piece

class cat {
    ...
    //After
    template <class Self>
    auto&& get_held_toy(this Self&& self) {
        return self.held_toy_;
    }

    //After + forwarding
    template <class Self>
    auto&& get_held_toy(this Self&& self) {
        return std::forward<Self>(self).held_toy_;
    }
};

I tried the following code, it still compiles. Is there any specific reason to use template function in Microsoft blog's example?

godbolt

// turn on c++23
class cat {
    ...
    //After + forwarding
    auto&& get_held_toy(this cat&& self) {
        return std::forward<Self>(self).held_toy_;
    }
};
2

There are 2 best solutions below

0
Artyer On

With this declaration:

auto&& get_held_toy(this cat&& self) {

The explicit object parameter always has type cat&& (that is, an rvalue reference), so you could only call this function on rvalues:

auto c = cat{};
// c.get_held_toy();  // error
std::move(c).get_held_toy();
cat{}.get_held_toy();

It's basically the same as:

auto&& get_held_toy() && {

Which there is use for, but that isn't "deducing this" (it's just an explicit object parameter).

The real magic of:

template <class Self>
auto&& get_held_toy(this Self&& self) {

// Or
auto&& get_held_toy(this auto&& self) {

Is that the type of the object parameter, what used to be "*this", can be deduced (hence "deducing this") from how it is being called:

auto c = cat{};
toy_type& x = c.get_held_toy();  // mutable lvalue
const toy_type& y = std::as_const(c).get_held_toy();  // const lvalue
toy_type&& z = std::move(c).get_held_toy();  // xvalue

Without having to write 3 different overloads.

0
Swift - Friday Pie On

First, this Self allows to determine the actual qualifier of object it was called for. One definition for all possible overloads without possiblity to cause ambigous declaration. For template function && is a forwarding reference. If you remove template, you limit this function to xvalue\prvalue ones. It allows this type of recurrent behaviour:

class cat {
protected:
    ball held_toy_;
public:

    //After + forwarding
    template <class Self>
    auto&& get_held_toy(this Self&& self) {
        return std::forward<Self>(self).held_toy_;
    }
};

class displacer :  public cat
{
protected:
   player held_toy_;
   friend class cat;
};

int main()
{
    cat       mew;
    displacer boo;
    std::cout << typeid(mew.get_held_toy()).name() << std::endl;
    std::cout << typeid(cat().get_held_toy()).name() << std::endl;
    std::cout << typeid(boo.get_held_toy()).name() << std::endl; // displacer::held_toy_
}

Second, your way to define it is ill-formed in case of std::forward<Self>(self).held_toy_; - Self in not cat or displacer, substitution for std::forward fails.