Should I return const objects?

C++

C++ Problem Overview


In Effective C++ Item 03, Use const whenever possible.

class Bigint
{
  int _data[MAXLEN];
  //...
public:
  int& operator[](const int index) { return _data[index]; }
  const int operator[](const int index) const { return _data[index]; }
  //...
};

const int operator[] does make difference from int& operator[].

But what about:

int foo() { }

and

const int foo() { }

Seems like that they're the same.

My question is, why we use const int operator[](const int index) const instead of int operator[](const int index) const ?

C++ Solutions


Solution 1 - C++

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

Solution 2 - C++

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.

Solution 3 - C++

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.

Solution 4 - C++

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;

Solution 5 - C++

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.

Solution 6 - C++

When that book was written, the advice was of little use, but did serve to prevent the user writing, for example, foo() = 42; and expecting it to change something persistent.

In the case of operator[], this can be slightly confusing if you don't also provide a non-const overload that returns a non-const reference, although you can perhaps prevent that confusion by returning a const reference or a proxy object instead of a value.

These days, it's bad advice, since it prevents you from binding the result to a (non-const) rvalue reference.

(As pointed out in the comments, the question is moot when returning a primitive type like int, since the language prevents you from assigning to an rvalue of such a type; I'm talking about the more general case that includes returning user-defined types.)

Solution 7 - C++

For primitive types (like int), the const-ness of the result does not matter. For classes, it might change the behaviour. For example, you might not be able to call a non-const method on the result of your function:

class Bigint {
    const C foo() const { ... }
     ...
}

Bigint b;
b.foo().bar();

The above is forbidden if bar() is not a const member function of C. In general, choose whichever makes sense.

Solution 8 - C++

Look on that:

const int operator[](const int index) const

the const on end of statement. It describe that this method can be called on constants.

In the other hand when you write only

int& operator[](const int index)

it can be called only on non-const instances and also provide:

big_int[0] = 10;

syntax.

Solution 9 - C++

One of your overloads is returning a reference to an item in the array, which you are then able to change.

int& operator[](const int index) { return _data[index]; }

The other overload is returning a value for you to use.

const int operator[](const int index) const { return _data[index]; }

Since you call each of these overloads in the same way, and one will never change the value when it's used.

int foo = myBigInt[1]; // Doesn't change values inside the object.
myBigInt[1] = 2; // Assigns a new value at index `

Solution 10 - C++

In the example of the array-indexing operator (operator[]) it does make a difference.

With

int& operator[](const int index) { /* ... */ }

you can use the indexing to directly change the entry in the array, e.g. using it like this:

mybigint[3] = 5;

The second, const int operator[](const int) operator is used to fetch the value only.

However, as return value from functions, for simple types like e.g. int it doesn't matter. When it does matter is if you are returning more complex types, say a std::vector, and don't want the caller of the function to modify the vector.

Solution 11 - C++

The const return type is not so important here. Since int temporaries are not modifiable, there's no observable difference in using int vs const int. You would see a difference if you used a more complex object which could be modified.

Solution 12 - C++

There are some good answers revolving around the technicality of the two versions. For a primitive value, it doesn't make a difference.

However, I've always considered const to be there for the programmer rather than the compiler. When you write const, you're explicitly saying "this shouldn't change". Let's face it, const can usually be circumverted anyway, right?

When you return a const, you're telling the programmer that uses that function that the value shouldn't change. And if he is changing it, he's probably doing something wrong, because he/she shouldn't have to.

EDIT: I also think "use const whenever possible" is bad advice. You should use it where it makes sense.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionabcdabcd987View Question on Stackoverflow
Solution 1 - C++James KanzeView Answer on Stackoverflow
Solution 2 - C++Bartek BanachewiczView Answer on Stackoverflow
Solution 3 - C++sbiView Answer on Stackoverflow
Solution 4 - C++AndreyView Answer on Stackoverflow
Solution 5 - C++Kerrek SBView Answer on Stackoverflow
Solution 6 - C++Mike SeymourView Answer on Stackoverflow
Solution 7 - C++Grzegorz HermanView Answer on Stackoverflow
Solution 8 - C++HaulethView Answer on Stackoverflow
Solution 9 - C++AestheteView Answer on Stackoverflow
Solution 10 - C++Some programmer dudeView Answer on Stackoverflow
Solution 11 - C++Ken Wayne VanderLindeView Answer on Stackoverflow
Solution 12 - C++Luchian GrigoreView Answer on Stackoverflow