2

Background

The following code block appears in Scott Meyers' famous book "Effective C++" Item 3:

class TextBlock {
public:
    ...
    const char& operator[](std::size_t position) const
    {
        ...    // do bounds checking
        ...    // log access data
        ...    // verify data integrity
        return text[position];
    }
    char& operator[](std::size_t position)
    {
        ...    // do bounds checking
        ...    // log access data
        ...    // verify data integrity
        return text[position];
    }
    ...
private:
    std::string text;
};

The author states that in the above implementation, the contents of the const and the non-const overloads are essentially the same. In order to avoid code duplication, it could be simplified like this:

class TextBlock {
public:
    ...
    const char& operator[](std::size_t position) const    // the same as before
    {
        ...
        ...
        ...
        return text[position];
    }
    char& operator[](std::size_t position)        // now just calls const op[]
    {
        return                                    // cast away const on
          const_cast<char&>(                      // op[]'s return type;
            static_cast<const TextBlock&>(*this)  // add const to *this's type;
              [position]                          // call const version of op[]
          );
    }
    ...
private:
    std::string text;
};

Questions

My questions are:

  • When shall we need an overload for const T& and another for T&&? (Here, T may be a template parameter or a class type, so T&& may or may not mean a universal reference) I can see that in the standard library, many classes provide both overloads. Examples are constructors of std::pair and std::tuple, there are tons of overloads. (Okay, I know that among the functions, one of them is the copy constructor and one of them is the move constructor.)

  • Is there a similar trick to share the implementations for the const T& and T&& overloads? I mean, if the const T&& overload returns an object that is copy-constructed, and the T&& overload returns something that is move-constructed, after sharing the implementation, this property must still hold. (Just like the above trick: const returns const and non-const returns non-const, both before and after implementation sharing)

Thanks!

Clarifications

The two overloads I'm referring to should look like:

Gadget f(Widget const& w);
Gadget f(Widget&& w);

It is nothing related to returning by rvalue references, that is:

Widget&& g(/* ... */);

(By the way, that question was addressed in my previous post)

In f() above, if Gadget is both copy-constructible and move-constructible, there's no way (except from reading the implementation) to tell whether the return value is copy-constructed or move-constructed. It is nothing to deal with Return Value Optimization (RVO) / Named Return Value Optimization (NRVO). (See my previous post)

References

Effective C++

std::pair::pair

std::tuple::tuple

When is it a good time to return by rvalue references?

Community
  • 1
  • 1

1 Answers1

2

• When shall we need an overload for const T& and another for T&&?

Basically, when moving gives you a performance gain, there should be also a move constructor. The same holds for functions in which you'd otherwise needed an expensive copy.

In your example, where you return a reference to a char, it is however not advisable to also set up a function which returns an rvalue reference. Rather, return by value and rely on the compiler's ability to apply RVO (see e.g. here)

•Is there a similar trick to share the implementations for the const T& and T&& overloads?

I often found it useful to set up a constructor or function using a universal reference (I'm lazy), i.e. something like

struct MyClass
{
    template<typename T /*, here possibly use SFINAE to allow only for certain types */>
    MyClass(T&& _t) : t(std::forward<T>(_t)) {}
private:
    SomeType t;
};

EDIT: Regarding your update: if you have an expensive copy of Widget in your function f, it is adviceable also to provide an overload taking a Widget&&.

Gadget f(Widget const& w)
{
    Widget temp = w;  //expensive copy
}
Gadget f(Widget&& w)
{
    Widget temp = std::move(w);  //move
}

You could combine both function using a function template like this

template<typename WidgetType
       // possibly drop that SFINAE stuff
       // (as it is already checked in the first assignment)
       , typename std::enable_if<std::is_convertible<std::remove_reference_t<WidgetType>, Widget>::value> >
Gadget(WidgetType&& w)
{
    Widget temp = std::forward<WidgetType>(w);
    //or
    std::remove_reference_t<WidgetType> temp2 = std::forward<WidgetType>(w);
}

... I didn't say it's nicer ;-).


EDIT 2: See also this thread, which addresses your question much more thouroughly.

Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • Updated. Please see my clarifications above. Thanks! – Siu Ching Pong -Asuka Kenji- Mar 30 '15 at 13:28
  • Thanks for your answer! You mentioned "performance gain" and declared `temp` in your code. Did you assume that `temp` is to be modified in `f()`? If `temp` is not going to be modified, `w` could be used directly in either `f()`, `temp` becomes unnecessary, causing `f(Widget&&)` to be unnecessary too, which means defining `f(Widget const&)` alone is sufficient, right? Since both lvalue references and rvalue references are references (they do not copy anything), and both `f(Widget const&)` and `f(Widget&&)` are compatible with `f(h())`, where `h()` is something like `Widget h()`. Am I correct? – Siu Ching Pong -Asuka Kenji- Mar 30 '15 at 15:07
  • @SiuChingPong-AsukaKenji-: I didn't mean to assume anything on the function `f` beside that it is creating a new object based on the inputted object -- for whatever reason (a constructor is one common example). Then, passing a temporary object `Widget()` to `f(Widget const&)` leads to a copy inside the function (--at least in theory, I don't know whether modern compilers don't create the object in place). If this copy is expensive while at the same time the move is cheap, it is sensible to supply an overload `f(Widget &&)` ... and also if you're pedantic like me :-) – davidhigh Mar 30 '15 at 17:01
  • Sorry, but I'm afraid binding a const lvalue reference to a temporary object does not copy it. If the temporary object is copy-constructed, then `f(Widget);` and `f(Widget const&)` would have meant the same when the parameter is a temporary / unnamed object. Then, the `const` in `f(Widget const&)` would be meaningless: if we're modifying a copied object, why do we need `const`? Here is a post about this (the answer with 42 votes explains my point): http://stackoverflow.com/q/1565600/142239 – Siu Ching Pong -Asuka Kenji- Mar 31 '15 at 05:39
  • 1
    @SiuChingPong-AsukaKenji-: we're talking past each other, I think. Not the binding to the reference copies the object. I've assumed that somewhere *in the function* a copy/move is done ... for whatever reason. The basic example for this is a constructor: assume that inside the class `A` you hold an object `b` of type `B`. If you now call `A(B const& _b) : b(_b) {} ` via `A(B())`, it will lead to a copy of the newly created object `B()`. You can avoid this with the move constructor. – davidhigh Mar 31 '15 at 07:24