Same Function with Const and Without - When and Why

Same function with const and without - When and why?

But why would you have both functions in one and the same class definition?

Having both allows you to:

  • call the function on a mutable object, and modify the result if you like; and
  • call the function on a const object, and only look at the result.

With only the first, you couldn't call it on a const object. With only the second, you couldn't use it to modify the object it returns a reference to.

And how does the compiler distinguish between these?

It chooses the const overload when the function is called on a const object (or via a reference or pointer to const). It chooses the other overload otherwise.

I believe that the second f() (with const) can be called for non-const variables as well.

If that were the only overload, then it could. With both overloads, the non-const overload would be selected instead.

Both const and non-const version of the same function - an anti-pattern?

No, not always.

There are legitimate uses for this pattern. For example, suppose you are writing a collection, and the code for retrieving an element is fairly complex (e.g. a hash table). You don't want to duplicate all the code, but you also want your collection to be able to be used as both const and non-const.

So, you might do something like this:

struct HashTable {
...

const Value &get(Key key) const {
... complex code for retrieving the key
}

Value &get(Key key) {
return const_cast<Value &>(
static_cast<const HashTable *>(this)->get(key)
);
}
};

Here, the const_cast<> is not really a lie. Since your function is non-const, you know that it can be called only if the object pointed to by this is also non-const. Hence, casting the constness away is valid.

(of course, similarly to this situation, you can call a non-const method by casting away the const-ness of a const instance, but at that point its the user of your class who has already introduced undefined behavior, so you are covered as long as your class is being used correctly.)

Elegant solution to duplicate, const and non-const, getters?

I recall from one of the Effective C++ books that the way to do it is to implement the non-const version by casting away the const from the other function.

It's not particularly pretty, but it is safe. Since the member function calling it is non-const, the object itself is non-const, and casting away the const is allowed.

class Foo
{
public:
const int& get() const
{
//non-trivial work
return foo;
}

int& get()
{
return const_cast<int&>(const_cast<const Foo*>(this)->get());
}
};

C++ const and mutable functions with the same name

print_obj(obj1); 
print_obj(obj2);

The function to be called is evaluated on the basis of the cv-qualifier(const/volatile) of the passed object. Note that cv-qualifiers are considered while function overload resolution.

If passed object is const, function receiving const argument is selected.If passed object is non-const then function receiving non-const argument is selected.


obj1.Print();   
obj2.Print();
pobj1->Print();

If the object is const then only call const member function can be called.

If the object is non-const then the non-const version is prefferred over the const version.

The rules are clearly specified by the standard.

Reference:

C++03 standard:

§13.3.1 Candidate functions and argument lists:

For non-static member functions, the type of the implicit object parameter is “reference to cv X” where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration. [Example: for a const member function of class X, the extra parameter is assumed to have type “reference to const X”. ]

So if the object is const compiler will pick version of member function which has implicit object parameter of type reference to const Object which is const version of Print().

Why the need for both const reference and const member function?

  1. return a const reference to prevent the function to change the returning value
  2. a const member function that cannot modify the object. in this case, it would be like this

This could have been a bit more clearer, I'll try my shot at explaining it better.

Returning a const reference prevent the returned object to be mutated by callers.

Here's an example:

// let get_name be `const std::vector<std::string>& get_name()`

np1.get_name().size(); // ok, size is a const function of vector
np1.get_name().push_back("hello"); // ERROR! push_back is not a const function of vector

So indeed, a caller cannot change the name vector. The return type is const qualified.

However, if the function get_name itself is not const qualified (not the return type), then it is allowed to change name from the class itself.

You see, member functions receive a hidden this parameter, which is a pointer to the object being called on. The pointer can either point to a const object, or a mutable object. Here's the comparison:

// returning const ref, callers cannot change the name vector
const std::vector<std::string>& get_name() {
// The function receive a `this` that points to a mutable,
// we can change the name from the inside
this->name.push_back("another");
return name;
}

// the `this` pointer points to const -----v---v
const std::vector<std::string>& get_name() const {
this->name.push_back("another"); // ERROR! Cannot mutate member of const object
return name;
}

Const-qualified member function are really useful for the caller, as it knows that whenever this function is called, its state won't change.

For example, not only you know that the vector::size() function won't mutate the vector, but the compiler guarantees it since it's a const qualified member function.

And for the last bit, the code you posted here:

vector<string> get_name() const { return name; } 

This will not return a reference, but will make a copy. The caller can mutate the copy however it wants, but cannot mutate name itself.

Here's an example of a copy mutated:

auto name_copy = np1.get_name();

name_copy.push_back("another name"); // works, we mutated the copy by adding an element

How do I remove code duplication between similar const and non-const member functions?

Yes, it is possible to avoid the code duplication. You need to use the const member function to have the logic and have the non-const member function call the const member function and re-cast the return value to a non-const reference (or pointer if the functions returns a pointer):

class X
{
std::vector<Z> vecZ;

public:
const Z& z(size_t index) const
{
// same really-really-really long access
// and checking code as in OP
// ...
return vecZ[index];
}

Z& z(size_t index)
{
// One line. One ugly, ugly line - but just one line!
return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
}

#if 0 // A slightly less-ugly version
Z& Z(size_t index)
{
// Two lines -- one cast. This is slightly less ugly but takes an extra line.
const X& constMe = *this;
return const_cast<Z&>( constMe.z(index) );
}
#endif
};

NOTE: It is important that you do NOT put the logic in the non-const function and have the const-function call the non-const function -- it may result in undefined behavior. The reason is that a constant class instance gets cast as a non-constant instance. The non-const member function may accidentally modify the class, which the C++ standard states will result in undefined behavior.

Const function calling non const or vice versa (to avoid duplication)?

If you have to make a function that is const-agnostic, and avoids duplication, one neat way to do it is delegating implementation to a template, for example

class Foo {
private:

int my_int;
template <typename ThisPtr>
static auto& get(ThisPtr this_ptr) {
return this_ptr->my_int;
}

public:
int& get() {
return get(this);
}

const int& get() const {
return get(this);
}
};

This way you are free from the fear associated with using const_cast, mutable and other stuff that goes into trying to reduce code duplication in cases like this. If you get something wrong, the compiler will let you know.

Why using the const keyword before and after method or function name?

const T& get_data() const { return data_; }
^^^^^

means it will return a const reference to T (here data_)

Class c;
T& t = c.get_data() // Not allowed.
const T& tc = c.get_data() // OK.




const T& get_data() const { return data_; }
^^^^^

means the method will not modify any member variables of the class (unless the member is mutable).

void Class::get_data() const {
this->data_ = ...; // is not allowed here since get_data() is const (unless 'data_' is mutable)
this->anything = ... // Not allowed unless the thing is 'mutable'
}

What is meant with const at end of function declaration?

A "const function", denoted with the keyword const after a function declaration, makes it a compiler error for this class function to change a member variable of the class. However, reading of a class variables is okay inside of the function, but writing inside of this function will generate a compiler error.

Another way of thinking about such "const function" is by viewing a class function as a normal function taking an implicit this pointer. So a method int Foo::Bar(int random_arg) (without the const at the end) results in a function like int Foo_Bar(Foo* this, int random_arg), and a call such as Foo f; f.Bar(4) will internally correspond to something like Foo f; Foo_Bar(&f, 4). Now adding the const at the end (int Foo::Bar(int random_arg) const) can then be understood as a declaration with a const this pointer: int Foo_Bar(const Foo* this, int random_arg). Since the type of this in such case is const, no modifications of member variables are possible.

It is possible to loosen the "const function" restriction of not allowing the function to write to any variable of a class. To allow some of the variables to be writable even when the function is marked as a "const function", these class variables are marked with the keyword mutable. Thus, if a class variable is marked as mutable, and a "const function" writes to this variable then the code will compile cleanly and the variable is possible to change. (C++11)

As usual when dealing with the const keyword, changing the location of the const key word in a C++ statement has entirely different meanings. The above usage of const only applies when adding const to the end of the function declaration after the parenthesis.

const is a highly overused qualifier in C++: the syntax and ordering is often not straightforward in combination with pointers. Some readings about const correctness and the const keyword:

Const correctness

The C++ 'const' Declaration: Why & How



Related Topics



Leave a reply



Submit