Should I return const objects?

Top level cv-qualifiers on return types of non class type are ignored. Which means that even if you write:

int const foo();

The return type is int. If the return type is a reference, of course, the const is no longer top level, and the distinction between:

int& operator[]( int index );

and

int const& operator[]( int index ) const;

is significant. (Note too that in function declarations, like the above, any top level cv-qualifiers are also ignored.)

The distinction is also relevant for return values of class type: if you return T const, then the caller cannot call non-const functions on the returned value, e.g.:

class Test
{
public:
    void f();
    void g() const;
};

Test ff();
Test const gg();

ff().f();             //  legal
ff().g();             //  legal
gg().f();             //  **illegal**
gg().g();             //  legal

You should clearly distinguish between the const usage applying to return values, parameters and the function itself.

Return values

  • If the function returns by value, the const doesn't matter, as the copy of the object is being returned. It will however matter in C++11 with move-semantics involved.
  • It also never does matter for basic types, as they are always copied.

Consider const std::string SomeMethod() const. It won't allow the (std::string&&) function to be used, as it expects non-const rvalue. In other words, the returned string will always be copied.

  • If the function returns by reference, const protects the returned object from being modified.

Parameters

  • If you pass a parameter by value, the const prevents the modification of given value by function in function. The original data from parameter can't be modified anyway, as you only have copy.
  • Note that since the copy is always created, the const has only meaning for function body, thus, it's checked only in function definition, not in declaration(interface).
  • If you pass a parameter by reference, the same rule as in return values applies

Function itself

  • If the function has const at the end, it can only run other const functions, and can't modify or allow modification of class data. Thus, if it returns by reference, the reference returned must be const. Only const functions can be called on object or reference to object which is const itself. Also the mutable fields can be changed.
  • The behavior created by the compiler changes this reference to T const*. The function can always const_cast this, but of course this shouldn't be done and is considered unsafe.
  • It's of course sensible to only use this specifier on class methods; global functions with const at the end will raise compilation error.

Conclusion

If your method doesn't and never will modify the class variables, mark it as const and be sure to meet all the critieria needed. It will allow more cleaner code to be written, thus keeping it const-correct. However, putting const everywhere without giving it any thought certainly isn't the way to go.


There is little value in adding const qualifications to non-reference/non-pointer rvalues, and no point in adding it to built-ins.

In the case of user-defined types, a const qualification will prevent callers from invoking a non-const member function on the returned object. For example, given

const std::string foo();
      std::string bar();

then

foo().resize(42);

would be forbidden, while

bar().resize(4711);

would be allowed.

For built-ins like int, this makes no sense at all, because such rvalues cannot be modified anyway.

(I do remember Effective C++ discussing making the return type of operator=() a const reference, though, and this is something to consider.)


Edit:

It seems that Scott did indeed give that advice. If so, then due to the reasons given above, I find it questionable even for C++98 and C++03. For C++11, I consider it plainly wrong, as Scott himself seems to have discovered. In the errata for Effective C++, 3rd ed., he writes (or quotes others who complained):

The text implies that all by-value returns should be const, but cases where non-const by-value returns are good design are not difficult to find, e.g., return types of std::vector where callers will use swap with an empty vector to "grab" the return value contents without copying them.

And later:

Declaring by-value function return values const will prevent their being bound to rvalue references in C++0x. Because rvalue references are designed to help improve the efficiency of C++ code, it's important to take into account the interaction of const return values and the initialization of rvalue references when specifying function signatures.


You might miss the point of Meyers' advice. The essential difference is in const modifier for the method.

This one is a non-const method (notice no const at the end) which means it is allowed to modify the state of the class.

int& operator[](const int index)

This one is a const method (notice const at the end)

const int operator[](const int index) const

What about types of the parameter and the return value, there is a minor difference between int and const int, but it is not relevant to the point of the advice. What you should pay attention to that the non-const overload returns int& which means you can assign to it, e.g. num[i]=0, and the const overload returns non-modifiable value (no matter if the return type is int or const int).

In my personal opinion, if an object is passed by value, const modifier is superfluous. This syntax is shorter and achieves the same

int& operator[](int index);
int operator[](int index) const;

The primary reason for returning values as const is so that you can't say something like foo() = 5;. This isn't actually an issue with primitive types, since you can't assign to rvalues of primitive types, but it is an issue with user-defined types (like (a + b) = c;, with an overloaded operator+).

I've always found the justification for that rather flimsy. You can't stop someone who's intent on writing awkward code, and this particular type of coercion has no real benefit in my opinion.

With C++11, there's actually a good deal of harm this idiom is doing: Returning values as const prevents move optimisations and should thus be avoided whenever possible. Basically, I'd now consider this an anti-pattern.

Here's a tangentially related article concerning C++11.