Channels ▼

Andrew Koenig

Dr. Dobb's Bloggers

Object Swapping, Part 5: A Fine Point About Overloading

April 12, 2012

Last week, a sharp-eyed reader pointed out a subtle pitfall in the code that I presented. The problem was specifically in this part of the code:

 
Thing& operator=(Thing t) {
           swap(t);
           return *this;
}
Thing& operator=(Thing&& t) { swap(t); }

and its cause and cure are subtle enough to be worth understanding carefully.

If we have a function that accepts a Thing as its argument, there are four common ways of declaring that function. The fourth of these forms is new in C++11:

 
void f1(Thing);
void f2(Thing&);
void f3(const Thing&);
void f4(Thing&&);

The first of these forms is often referred to as "call by value" — in general, the parameter is initialized as a copy of the argument. The second is "call by reference," and requires that the argument be an lvalue. We might think of the third form as "call by const reference," and does not require the argument to be an lvalue.

It is common to overload functions that look like f2 and f3. Doing so does not create an ambiguity, because if it is possible for an argument to match both f2 and f3, the compiler will choose f2. This choice comes about because in order to choose f3, the compiler would have to add const to the argument. In effect, f2 is a better match than f3 in those cases where f2 is a match at all.

In other words: Suppose we declare

 
void f(Thing&);
void f(const Thing&);

and we call f. If f's argument is an lvalue, it is either const or not. If the argument is not const, the first version of f is a better match than the second (because matching the first version does not require adding const to the argument's type); if the argument is const, only the second version of f matches. If the argument is not an lvalue, then again only the second version of f matches. No argument to f can create an ambiguity.

Now let's look at f4. Its parameter is an rvalue reference, which is considered to be an exact match for any rvalue, but which cannot match an lvalue at all. So look what happens if f1 and f4 are overloaded:

void f(Thing);
void f(Thing&&);

If we call f with an lvalue:

Thing t;
 
f(t);              // calls f(Thing)

there is no problem: The second version of f will not accept an lvalue, so only the first version can be called. However, if we call it with an rvalue:

 
Thing tf();
f(tf());                    // Ambiguous
 

then the call is ambiguous: Both versions of f can accept an rvalue, and neither is better than the other.

One way to resolve this ambiguity is to use the same technique as we did in the first version of f that we saw, namely to overload it this way:

 
void f(const Thing&);
void f(Thing&&);
 

Now, as before, the second version of f will be preferred to the first version in those cases where it can be used at all:

 
Thing tf();
f(tf());                    // Calls f(Thing&&)

If we now go back to apply this overloading technique to our definition of Thing, we have a problem:

 
Thing& operator=(const Thing& t) {
           swap(t);     // Error
           return *this;
}

The problem, of course, is that swap expects a modifiable lvalue, and t is not modifiable because it is const. In effect, we have removed the copy that we were formerly making when we passed the argument to operator=; to make the code work again, we have to make the copy explicitly. The most straightforward way of doing so is:

 
Thing& operator=(const Thing& t) {
Thing temp = t; 

      swap(temp);

      return *this;
}

This discussion has been rather involved. The main point to take away from it is that if you want to overload copy and move versions of a function, be it a copy constructor, assignment operator, or anything else, the right way to do so is to overload const Thing& and Thing&&, not Thing and Thing&&. Moreover, the ambiguity that the first of these forms creates is generally not detected until you try to use the function in an ambiguous way — a fact that makes such errors particularly hard to catch.

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video