3

Possible Duplicate:
Move constructor signature

struct X
{
    X(X&);         // (1)
    X(X&&);        // (2)
    X(const X&);   // (3)
    X(const X&&);  // (4)
};

Are there any situations where (4) will be picked in overload resolution?

Community
  • 1
  • 1
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319

2 Answers2

9

Yes. One situation is when you have a function with a const return value:

const X f();

X x(f());
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Your answer is correct - but a const return value makes no sense right? Is there ever a use of making a return value const? – Andrew Tomazos Dec 31 '12 at 11:00
  • 7
    No, it doesn't make much sense. Some people got in the habit of doing it to avoid nonsensical things like `f() = x` in C++03, but nowadays that habit has to go away because it inhibits move semantics. – R. Martinho Fernandes Dec 31 '12 at 11:02
  • @R.MartinhoFernandes I believe there are cases where it does make sense. If you have time, please check my answer and the comments below it, and correct me if I am wrong. Thank you in a advance. – Andy Prowl Dec 31 '12 at 13:14
  • There are cases where it makes sense, when overloading `+` for instance, you don't want to allow `x + y = z`. – K-ballo Dec 31 '12 at 20:51
  • @K-ballo but you want to allow `foo(x+y)` to move, no? – R. Martinho Fernandes Dec 31 '12 at 20:59
  • @R. Martinho Fernandes: If I have to choose between one or the other, then I prefer forbidding `x + y = z` even if it inhibits move semantics... Code correctness is more important than optimizations. But with member function overloads on _rvrefs_ would be enough to get the best of both worlds, loosing nothing so... – K-ballo Dec 31 '12 at 21:03
  • @K-ballo I agree that correctness but is more important but I don't think that is such an insidious hard to track mistake to make me give up move semantics (and move semantics are not always just an optimization). – R. Martinho Fernandes Jan 01 '13 at 05:06
  • @R. Martinho Fernandes: I should have chosen my words better, specially considering I just wrote a piece on how move semantics are not about an optimization and that the optimization is just a nice side effect. Anyway, in the _move semantics_ world the right way to avoid nonsensical things is with rvalue overloads, not anymore by returning `const` objects, so my previous comment is mute. Please disregard. – K-ballo Jan 01 '13 at 05:11
2

Another situation is when you are applying std::move to a const object, as in the following example:

#include <iostream>

using namespace std;

struct X
{
    X() { cout << "default" << endl; }
    X(X&) { cout << "non const copy" << endl; }    // (1)
    X(X&&) { cout << "non const move" << endl; }   // (2)
    X(const X&) { cout << "const copy" << endl; }  // (3)
    X(const X&&){ cout << "const move" << endl; }  // (4)
};

void f(X const x)
{
}

int main()
{
    X const x;
    f(std::move(x));

    return 0;
}

The case mentioned in a previous answer (X const f()) is probably less common, because the move constructor is most of the times elided by the compiler when doing RVO (this can be done even if the constructor has side-effects).

Moving a const object into another const object as a logical operation does make sense, and if you do not define a const move constructor then you won't be allowed to do that. Moving is just transferring ownership, so it seems to me that as a logical operation it should be supported.

Although it is true that from a high-level viewpoint you are not supposed to modify a const object and indeed moving an object requires modifying it (in most cases), there are well-known similar situations where it is OK to modify a const object "behind the scenes" for the purpose of realizing a higher-level conceptual operation. Think for instance of a const member function that needs to lock a non-const member mutex: you may declare the mutex variable as mutable to treat it as a non-const object.

So even in this case I see it as a legitimate operation in a const move constructor (as long as it is needed) to modify mutable members of the moved object (perhaps some boolean flag to be checked by the object's destructor). Please notice, that if you remove the const constructors (3) and (4) from the code above, then it won't compile. If you remove only (4), the copy constructor (3) will be chosen - meaning you won't be able to move the const object.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 2
    You seem to be implying that a const move contructor should `const_cast` away the constness and then do a normal move. I don't think this is const correct at all. A const move constructor should do exactly the same as the copy constructor. It makes no sense to move a const object. – Andrew Tomazos Dec 31 '12 at 13:38
  • Why does it make no sense to move a `const` object? If I want to supply it in input to a function, why could I not move it rather than copying it? What you want is just *efficient transferring of ownership* and since you know you are not going to use the original `const` object anymore, moving it is the right choice. And if you want to move a `const` object you need a `const` move constructor, which might or might not (depending on whether it is necessary) apply `const_cast<>` – Andy Prowl Dec 31 '12 at 13:42
  • Do you have any references to back this up or is this your own interpretation and intuition? – Andrew Tomazos Dec 31 '12 at 13:49
  • It is my own interpretation and intuition, but it is solidly built on what I've read on move semantics from many reliable sources. According to those sources, moving an object means transferring ownership, and nowhere it is mentioned that this transfer of ownership is meaningful for non-const objects only. Why would it be so, if you think in terms of ownership transfer? And if you want to move a `const` object you need a `const` move constructor. – Andy Prowl Dec 31 '12 at 13:55
  • 3
    Modifying an object whose original declaration is `const` is Undefined Behavior. So a safe const move constructor would not change the source in any circumstances. But then it's the same as the copy constructor, and there's no point in declaring the overload. – aschepler Dec 31 '12 at 14:03
  • 1
    @aschepler Sorry, I don't think that is true. A `const` object may have `mutable` members that *can* be modified by a `const` move constructor. As I mentioned in a previous comment, the pattern is the same as when you want to lock a member mutex inside a `const` function. – Andy Prowl Dec 31 '12 at 14:06
  • 1
    Concrete example (sorry for bad formatting, don't know how to improve it): `struct X { [...] X(const X&& other){ other.flag = false; cout << "const move" << endl; } mutable bool flag = true; };` This code is legal. – Andy Prowl Dec 31 '12 at 14:09
  • @aschepler Your point about `const_cast<>` is correct, I will further edit my answer to only mention `mutable` variables. But it still makes sense to have `const` move constructors. – Andy Prowl Dec 31 '12 at 14:15
  • A move constructor isn't a "change of ownership". It means that the source value of the move is effectively destroyed by taking away its internal resources. Taking away resources from a value modifies it. You may not modify a const object. You cannot move from a constant source. – Andrew Tomazos Dec 31 '12 at 14:41
  • 1
    This is not completely correct: you may modify `mutable` values of a `const` objects. That makes it possible to move from a constant source. For instance I can imagine a vector class (not `std::vector` of course) whose `const` move constructor simply copies the data pointer and the size counter (and other stuff if necessary), and sets a `mutable` flag in the moved object to `false`, so that the destructor of that object won't try to deallocate the memory. No element-wise copy will occur, no double deallocation, so that's an effective, safe, and legal move of such a `const` vector. Am I wrong? – Andy Prowl Dec 31 '12 at 14:46
  • 1
    @AndrewTomazosFathomlingCorps: I think we could view it differently, you can execute a destructor on a `const` object right ? Well, after moving *from* an object, its state is unspecified; the only meaningful operations are assignment and destruction. I understand that moving from a `const` object does not seem to make sense at first glance, but if we remind ourselves that the object is not supposed to be used after the move, then its constness matters little, does it not ? As an example, think of `Big foo() { Big const b(...); /*do things*/; return b; }`: I don't see why I could not move... – Matthieu M. Dec 31 '12 at 16:16
  • @MatthieuM. I think @AndrewTomazosFathomlingCorps's point is that if you can't modify an object you can't change its state so that the object is perceived as "dead" by its assignment operator and destructor. This matters, because a destructor might do cleanup on the moved object *unless told not to do so*, and the only way to tell not to do so is to somehow modify the state of the object. This (Andrew's point) cannot be done because the object is `const` and you can't use const_cast<> since later attempts to modify it (e.g. assigning) lead to undefined behavior. But `mutable` members exist... – Andy Prowl Dec 31 '12 at 16:37
  • @MatthieuM.: What's wrong with this picture: `void f(const T& t) { g(move(t)); }`. If `const T&&` is ok to nullify and the constness can be ignored, than the client calling through f just got their parameter destroyed. Moving an object is a mutation. The destructor is not the same, the object isnt accessible by anyone after destruction, the compiler makes sure of it. – Andrew Tomazos Dec 31 '12 at 17:25
  • @AndrewTomazosFathomlingCorps: Well, in this case function `f()` *does not have exclusive ownership of* `t`, because it got just a reference to it; so it should not try to move it, because its clients might still want to use it. – Andy Prowl Dec 31 '12 at 17:37
  • @Andy the example you provided is not very compelling because it is completely artificial. You should provide an example that makes people see why they would want to use this (i.e. it needs meaning). I believe everyone involved knows *how* to do it, but we struggle to understand *why*. – R. Martinho Fernandes Dec 31 '12 at 20:08
  • My main problem understanding why this would be needed is the following: if you have `mutable` members that can be *observed* by the interface mutating them changes logical state, so to me that whole class design seems broken from the start anyway. If the `mutable` members cannot be *observed* then the transfer of ownership is *not actually observable*, so I question why a user would bother to do something that effectively *has no effects*. – R. Martinho Fernandes Dec 31 '12 at 20:12
  • @R.MartinhoFernandes: If the question is "why would we want to move a `const` object?" the answer is the same as for the question "why would we want to move a non-const object?": for efficiency reasons. – Andy Prowl Dec 31 '12 at 20:14
  • @R.MartinhoFernandes: Answering your second comment, I'm not sure I got your whole point, but in short, when you move an object you need to set its state in a way that allows its destruction and its assignment, right? Well, the main objection so far was: how would you ever do that, if you have a `const` object and you cannot modify its state? But `mutable` variables allow doing precisely that. They are *not* part of the *logical* state, they just allow the object to be in such state that makes it only destructible and assignable. – Andy Prowl Dec 31 '12 at 20:18
  • If they are not part of the logical state *exactly what are you moving here*? Remember that you cannot leave the source unusable because that would imply a change in logical state. So you have to effectively be left with two usable objects. I call that a *copy*. – R. Martinho Fernandes Dec 31 '12 at 20:19
  • All the rest. Think of a string class (call it `String`) that holds a data pointer to some dynamically allocated array of chars and a size counter. That's part of its logical state. Then you have a `mutable` boolean flag `isDead`. Client code declares a variable `const String s`. It wants to pass it in input to a function `foo()` that accepts a `String` by value and returns a `String` by value. It wants to do `String s1 = foo(move(s))`. The `const` move constructor of `String` gets called, copies the data pointer and the size counter, and sets `isDead` to `true` for the object being moved. – Andy Prowl Dec 31 '12 at 20:23
  • @AndyProwl And what does `isDead` do? – R. Martinho Fernandes Dec 31 '12 at 20:24
  • The destructor of the object that has been moved out finds the `isDead` flag set to `true` and won't try to deallocate the array of characters. Similarly, the assignment operator will not try to deallocate the array of characters. This makes sure the object is destructible and assignable. – Andy Prowl Dec 31 '12 at 20:25
  • But the object must be *in the same logical state* as before, not just destructible and assignable. That's what `const` means: its logical state does not change. However, the `s` string now is not usable anymore (its resources are not longer guaranteed to be valid). If you want to leave an object in an unusable state except for destruction and assignment, don't make it `const` in the first place. – R. Martinho Fernandes Dec 31 '12 at 20:27
  • After you move the `s` string, `s` can only be assigned or destructed and the client shall make no assumption on its state. Why should it matter if that state is the same or not, as long as it is a valid state? – Andy Prowl Dec 31 '12 at 20:29
  • "Why should it matter if that state is the same or not, as long as it is a valid state?" *Because that is the whole point of `const`*. If you are not going to keep it in the same state do not make it `const`. Your argument seems to me to amount to "it makes sense to move a const object if you make some nonsensical mistakes first". I do not find that compelling at all. – R. Martinho Fernandes Dec 31 '12 at 20:31
  • No, my argument is that by declaring an object as `const` i mean that its logical state is not going to be changed. If I invoke a method of that object, it shall not change its logical state. But if I move away a `const` object, it means I'm simply not going to care about that object anymore: I simply want to transfer its ownership to some other piece of code and get my old skeleton destructed. Why shouldn't I be able to do that by move? – Andy Prowl Dec 31 '12 at 20:33