Reputation: 2053
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
?
Upvotes: 82
Views: 33710
Reputation: 1915
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.
Upvotes: 2
Reputation: 224049
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.
Upvotes: 31
Reputation: 153909
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
Upvotes: 91
Reputation: 39370
You should clearly distinguish between the const usage applying to return values, parameters and the function itself.
Return values
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.
const
protects the returned object from being modified.Parameters
Function itself
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. this
reference to T const*
. The function can always const_cast
this
, but of course this shouldn't be done and is considered unsafe.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.
Upvotes: 44
Reputation: 258568
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.
Upvotes: 0
Reputation: 254431
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.)
Upvotes: 9
Reputation: 8594
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;
Upvotes: 18
Reputation: 19339
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.
Upvotes: 0
Reputation: 476980
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.
Upvotes: 16
Reputation: 409166
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.
Upvotes: 0
Reputation: 18850
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 `
Upvotes: 0
Reputation: 23556
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.
Upvotes: 0