Why Does a Function Declaration with a Const Argument Allow Calling of a Function with a Non-Const Argument

Why does a function declaration with a const argument allow calling of a function with a non-const argument?

Because it doesn't matter to the caller of the foo function whether foo modifies its copy of the variable or not.

Specifically in the C++03 standard, the following 2 snippets explain exactly why:

C++03 Section: 13.2-1

Two function declarations of the same name refer to the same function if they are in the same scope and
have equivalent parameter declarations (13.1).

C++03 Section: 13.1-3

Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.

Calling a non-const function with a const pointer as argument

int * const ptr=&x; 

declares a immutable pointer (you can't change the address stored in ptr) that points to a mutable int (you can change the value ptr points to).

void func(int *n)

takes a pointer to a mutable int by value, no problems here. The mutability of ptr is of no concern, because you take a copy anyway. Speaking of taking copies:

 n = &k; 

will only modify where the local variable n points. To reflfect changes outside the function you can pass the parameter by reference:

//             V
void func(int *&n)

And get the expected compiler error, because you can't bind ptr to a non-const reference.

Call non-const function on a const object

Yes. Remember two things-

  1. If the function is non-constant, it can only be called by a
    non-constant object.
  2. If the function is constant, it can be called on any objects (I mean
    any constant or non-constant objects.)

Reasons:

  1. If the function is non-constant, then the function is allowed to
    change values of the object on which it is being called. So the
    compiler doesn't allow to create this chance and prevent you to call
    a non-constant function on a constant object, as constant object
    means you cannot change anything of it anymore. So the compiler only
    allows you to call it on a non-constant object as this object can be
    modified.
  2. If the function is constant itself, then it is promising that it
    won't change anything of the object on which it is being called. So
    the compiler doesn't care whether you are calling a constant
    function on a constant or non-constant object as the function itself
    is unable to change the object.

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.

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

Passing const to function with non-const parameter

Take a look at this line:

return grade(midterm, final, median(hw));

hw here is a const reference to a vector, which is being passed to median. While it is true that you can't modify an object through a const reference, you can still make a copy of it through a const reference. So, since median requires to change the vector (by sorting it), it makes a copy for itself (the copy will be implicitly created under the hood) and sorts it instead.

Calling a non const function on a const instance in C++, is that possible?

The "Foo" class added above is purely for demonstrative purpose. In my real code, I can't edit the class, but the reason I want to make "f" const is for code-style/safety reason.

Since

void print() { cout << "hello\n"; }

isn't declared as const member function of Foo and you can't change that, you'll have to swallow that poor design, and access the print() function through a non const instance or reference.

To do that would need that print() is declared in the Foo class like

void print() const { cout << "hello\n"; }
// ^^^^^

I agree that the better code style would be to allow calling the print() function using a const instance of Foo.

But as you mentioned, if you can't change that, there's no way beyond using an explicit cast:

const_cast<Foo&>(f).print();

Anyways that won't add additional "safety" vs a simple call:

int main()
{
Foo f = get_foo();
f.print();
}

Passing non-const arguments to const function parameters in C

The issue you're having stems from your main function.

When you declare char *my_string = "my text";,you are creating a non-const pointer to a string literal. By design, string literals such as "my text" are immutable, and therefore const in the language. (In practice, the compilers usually put the string literals into a specific section of the executable which contains read-only memory, so attempting to modify the literal using the non-const pointer can lead to a segfault.)

By declaring a non-const pointer to the string literal, you end up with a pointer which you could use to modify the immutable string literal, which is considered undefined behavior in C.

See this question for more information.

The easiest way to solve this is to simply change char *my_string into const char *my_string.

Is it possible to call a consteval function with a non-const reference parameter?

but int& a forces it to be a runtime expression because of the missing const

No, that's a gross oversimplification and not how constant evaluation works. We can have moving parts (non-const qualified objects) as part of the evaluation. So long as they obey a strict set of rules that are checked when evaluating the constant expression. For example:

consteval void func(int& a) { a = 2; }
consteval int func2() { int b = 0; func(b); return b; }

int arr[func2()];

That's a pretty convoluted way of returning 2 for an array size, but it demonstrates the concept and one of the aforementioned rules. While doing constant evaluation, we introduced a helper variable b. We then proceeded to do something with it, modifying it, and return the result. That's the "evaluation" part.

The "constant" bit is in the expression truly being evaluatble during translation, all of its "inputs" are compile time constant (vacuously). And any non-const objects we used only came into being while doing the evaluation, not living longer than until it is completed.



Related Topics



Leave a reply



Submit