Is std::move(*this) a good pattern?
Asked Answered
I

2

40

In order to make this code with C++11 reference qualifiers work as expected I have to introduce a std::move(*this) that doesn't sound right.

#include<iostream>
struct A{
    void gun() const&{std::cout << "gun const&" << std::endl;}
    void gun() &&{std::cout << "gun&&" << std::endl;}
    void fun() const&{gun();}
    void fun() &&{std::move(*this).gun();} // <-- is this correct? or is there a better option
};

int main(){
    A a; a.fun(); // prints gun const&
    A().fun(); // prints gun&&
}

Something doesn't sound right about it. Is the std::move necessary? Is this a recommended use for it? For the moment if I don't use it I get gun const& in both cases which is not the expected result.

(It seems that *this is implicit and lvalue reference always, which makes sense but then the only way to escape to use move)

Tested with clang 3.4 and gcc 4.8.3.


EDIT: This is what I understand from @hvd answer:

  1. std::move(*this) is syntactically and conceptually correct

  2. However, if gun is not part of the desired interface, there no reason to overload the lv-ref and rv-ref version of it. And two functions with different names can do the same job. After all the ref-qualifiers matters at the interface level, which is generally only the public part.

struct A{
private:
    void gun() const{std::cout << "gun const&" << std::endl;}
    void gun_rv(){std::cout << "gun called from fun&&" << std::endl;}
public:
    void fun() const&{gun();}
    void fun() &&{gun_rv();} // no need for `std::move(*this)`.
};

But again, if gun is part of the (generic) interface then std::move(*this) is necessary, but only then. And also, even if gun is not part of the interface there readability advantages in not splitting the function gun as two differently named function and the cost of this is, well..., std::move(*this).

EDIT 2: In retrospect this is similar to the C++98 case of const and no-const overload of the same function. In some cases it makes sense to use const_cast (another form of cast) to not repeat code and have the two functions with the same name (https://mcmap.net/q/95065/-how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-functions). ... although in more complicated cases it makes sense to have auxiliary private functions that delegate the correct behavior for the interface function.

Interrupter answered 15/8, 2014 at 21:48 Comment(4)
If you don't want to use std::move, how about static_cast<A&&>(*this).gun()?Asante
@yohjp, or std::forward<A&&>(*this).gun() for that matter.Interrupter
I recommend making a member function called rvalue() that returns *this as an rvalue. Then you can call this->rvalue()->....Bernadine
@Mehrdad, would it be a function like written like this auto rvalue()->decltype(std::move(*this)){return std::move(*this);}?Interrupter
C
24

Yes, *this is always an lvalue, no matter how a member function is called, so if you want the compiler to treat it as an rvalue, you need to use std::move or equivalent. It has to be, considering this class:

struct A {
  void gun() &; // leaves object usable
  void gun() &&; // makes object unusable

  void fun() && {
    gun();
    gun();
  }
};

Making *this an rvalue would suggest that fun's first call to gun can leave the object unusable. The second call would then fail, possibly badly. This is not something that should happen implicitly.

This is the same reason why inside void f(T&& t), t is an lvalue. In that respect, *this is no different from any reference function parameter.

Colophony answered 15/8, 2014 at 22:7 Comment(8)
Yes, I understand that. So is std::move(*this) the way to use all && functions in the call chain? (I clarified the real question above)Interrupter
@Interrupter Yes, just like for other function parameters. The caller specified that the object can be safely moved from, but the called function itself also needs to do so, as the compiler cannot figure that out automatically. (Of course, since this is your own class type, you could also approach it differently: forward to a private destructive member function that can be called on lvalues. Since it's private, external users can't accidentally call it.)Colophony
I don't understand the other approach, using a private destructive (?) member.Interrupter
Just for the sake of exposition, the second call in the example should probably be std::move( * this ).gun();.Goshawk
@Interrupter Anything void gun() && can do, void gun() can do. So if you'd like to call the void gun() && version, what you could do instead, is implement void gunRvalue(), which is private and not generally safe to call on lvalues unless otherwise specified. You can call it from a public function like so: void gun() && { gunRvalue(); }, and anything else that you want to call the void gun() && version, can just call gunRvalue() directly.Colophony
@Goshawk The example is about a hypothetical version of the language in which gun() implicitly resolves to the rvalue version. But you're right that in the C++ that we actually have, that's probably what you should do.Colophony
I summarized my interpretation of all this as an edit in question. I hope I am interpreting this correctly.Interrupter
@Interrupter Yes, I think you did interpret that correctly, but I should add that although there is no need for rvalue overloads of private member functions, there may be circumstances in which such rvalue overloads make those functions or code more readable or easier to implement. In those cases, of course, do make use of them.Colophony
O
1

Yes, it's correct. A simple common example with the clone pattern:

class Foo
{
  public:
    Foo() = default;
    Foo(Foo const&) = default;
    Foo(Foo&&) = default;
    virtual ~Foo() = default;
    virtual Foo* clone() const & = 0;
    virtual Foo* clone() && = 0;
    virtual void do_something() = 0;
};

class Bar : public Foo
{
  public:
    Bar() = default;
    Bar(Bar const&) = default;
    Bar(Bar&&) = default;
    virtual ~Bar() = default;
    Bar* clone() const & override { return new Bar(*this); }
    Bar* clone() && override { return new Bar(std::move(*this)); }
    void do_something() override { std::cout << "Hello !" << std::endl; }
}
 
void scoubidou(Foo&& f)
{
  std::unique_ptr<Foo> ff(std::move(f).clone());
  ff->do_something();
}
Oao answered 17/4 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.