views:

207

answers:

3

First: where are std::move and std::forward defined? I know what they do, but I can't find proof that any standard header is required to include them. In gcc44 sometimes std::move is available, and sometimes its not, so a definitive include directive would be useful.

When implementing move semantics, the source is presumably left in an undefined state. Should this state necessarily be a valid state for the object? Obviously, you need to be able to call the object's destructor, and be able to assign to it by whatever means the class exposes. But should other operations be valid? I suppose what I'm asking is, if your class guarantees certain invariants, should you strive to enforce those invariants when the user has said they don't care about them anymore?

Next: when you don't care about move semantics, are there any limitations that would cause a non-const reference to be preferred over an rvalue reference when dealing with function parameters? void function(T&); over void function(T&&); From a caller's perspective, being able to pass functions temporary values is occasionally useful, so it seems as though one should grant that option whenever it is feasible to do so. And rvalue references are themselves lvalues, so you can't inadvertently call a move-constructor instead of a copy-constructor, or something like that. I don't see a downside, but I'm sure there is one.

Which brings me to my final question. You still can not bind temporaries to non-const references. But you can bind them to non-const rvalue references. And you can then pass along that reference as a non-const reference in another function.

void function1(int& r) { r++; }
void function2(int&& r) { function1(r); }
int main() { 
    function1(5); //bad
    function2(5); //good
}

Besides the fact that it doesn't do anything, is there anything wrong with that code? My gut says of course not, since changing rvalue references is kind of the whole point to their existence. And if the passed value is legitimately const, the compiler will catch it and yell at you. But by all appearances, this is a runaround of a mechanism that was presumably put in place for a reason, so I'd just like confirmation that I'm not doing anything foolish.

+2  A: 

included by utility


Here is the article I read about rvalues.

I can't help you with rest, sorry.

Ronny
+2  A: 

where are std::move and std::forward defined?

std::move and std::forward are declared in <utility>. See the synopsis at the beginning of section 20.3[utility].

When implementing move semantics, the source is presumably left in an undefined state.

It of course depends on how you implement the move-constructor and move-assignment operator. If you want to use your objects in standard containers, however, you have to follow the MoveConstructible and MoveAssignable concepts, which says that the object remains valid, but is left in unspecified state, i.e. you definitely can destroy it.

avakar
+6  A: 

First: where are std::move and std::forward defined?

See 20.3 Utility components, <utility>.


When implementing move semantics, the source is presumably left in an undefined state. Should this state necessarily be a valid state for the object?

Obviously, the object should still be destructibly. But further than that, i think it's a good idea to be still assignable. The Standard says for objects that satisfy "MoveConstructible" and "MoveAssignable":

[ Note: rv remains a valid object. Its state is unspecified. — end note ]

This would mean, i think, that the object can still participate in any operation that doesn't state any precondition. This includes CopyConstructible, CopyAssignable, Destructible and other things. Notice that this won't require anything for your own objects from a core language perspective. The requirements only take place once you touch Standard library components that state these requirements.


Next: when you don't care about move semantics, are there any limitations that would cause a non-const reference to be preferred over an rvalue reference when dealing with function parameters?

This, unfortunately, crucially depends on whether the parameter is in a function template and uses a template parameter:

void f(int const&); // takes all lvalues and const rvalues
void f(int&&); // can only accept nonconst rvalues

However for a function template

template<typename T> void f(T const&);
template<typename T> void f(T&&);

You can't say that, because the second template will, after being called with an lvalue, have as parameter of the synthesized declaration the type U& for nonconst lvalues (and be a better match), and U const& for const lvalues (and be ambiguous). To my knowledge, there is no partial ordering rule to disambiguate that second ambiguity. However, this is already known.

-- Edit --

Despite that issue report, i don't think that the two templates are ambiguous. Partial ordering will make the first template more specialized, because after taking away the reference modifiers and the const, we will find that both types are the same, and then notice that the first template had a reference to const. The Standard says (14.9.2.4)

If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transfor-mations above) and if the type from the argument template is more cv-qualified than the type from the parameter template (as described above) that type is considered to be more specialized than the other.

If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template.

This makes the T const& template the winner of partial ordering (and GCC is indeed correct to choose it).

-- Edit End --


Which brings me to my final question. You still can not bind temporaries to non-const references. But you can bind them to non-const rvalue references.

This is nicely explained in this article. The second call using function2 only takes nonconst rvalues. The rest of the program won't notice if they are modified, because they won't be able to access those rvalues afterwards anymore! And the 5 you pass is not a class type, so a hidden temporary is created and then passed to the int&& rvalue reference. The code calling function2 won't be able to access that hidden object here, so it won't notice any change.

A different situation is if you do this one:

SomeComplexObject o;
function2(move(o));

You have explicitly requested that o is moved, so it will be modified according to its move specification. However moving is a logically non-modifying operation (see the article). This means whether you move or not shouldn't be observable from the calling code:

SomeComplexObject o;
moveit(o); // #1
o = foo;

If you erase the line that moves, behavior will still be the same, because it's overwritten anyway. This however means that code that uses the value of o after it has been moved from is bad, because it breaks this implicit contract between moveit and the calling code. Thus, the Standard makes no specification about the concrete value of a moved from container.

Johannes Schaub - litb
Thank you for the elaborate response. I think I understand things less than I did before, but through no fault of your own. According to that article, a function such as `void f(int` can't accept lvalues unless they are explicitly cast? That is definitely not the behavior I've been seeing, so I assume this is a shortcoming in gcc-4.4's implementation. Which makes all the tests I've been doing to understand how everything interacts pretty much worthless :-/
Dennis Zickefoose
Johannes Schaub - litb