How can one invoke the second (overloaded) function?
(This example is present in Savitch's C++ textbook.)
(1) int& f(); // will be used in any l-value invocation
(2) const int& f() const; // will be used in any r-value invocation
I thought the first one is invoked in (a) and the second one in (b). But it is not.
(a) f() = 123; // the first one is invoked.
(b) f() + 3; // the first one is also invoked.
Only member functions can be const. So let's assume f actually is a member function in the textbook.
When the compiler has a choice between a const and a non const member function it will only use the const one if it has to. This is when the object the function is called on is const.
class A {
public:
int &f();
const int& f() const;
};
void func()
{
A a;
a.f(); // calls non const version
const A ca;
ca.f(); // call const version
}
(1) int& f(); // will be used in any l-value invocation
(2) const int& f() const; // will be used in any r-value invocation
These comments are wrong. If they appear in a textbook then I would recommend getting a different textbook.
Version 2 will be used when the expression denoting the object has const type, otherwise version 1 is used. It is nothing at all to do with lvalues and rvalues. Using Eelke's class definition:
A().f(); // invokes (1) on an rvalue
A const a;
a.f(); // invokes (2) on an lvalue
In your examples you don't actually show whether you are working on a const instance or not, but judging by your results, both must have been on a non-const one.
Overloading works on function parameters not by return type.
double f();
int f();
These are not overloading.
But these are:
double f();
int f(int);
Related
1. In global scope, this gives error: redefinition of 'f'
#include <iostream>
using namespace std;
void f(int x) { cout << "f" << endl; }
void f(const int x) { cout << "f (const)" << endl; } // error: redefinition of 'f'
int main() { }
2. Defining two copy constructors (one with const, the other without) compiles
#include <iostream>
using namespace std;
class Foo {
public:
Foo(const Foo&) { cout << "copy (const)" << endl; }
Foo(Foo&) { cout << "copy" << endl; }
};
int main() { }
Question
Why is #1 a redefinition error but #2 is not?
For the second example, is there a use case for defining two copy constructors (one with const the other without)?
Only the top-level constness is ignored on parameters when checking if two functions are the same.
What does "top-level" constness mean? It means that something is actually const, as reported by std::is_const_v.
For example int *const is top-level const (because the pointer itself is const), and const int * is not (because the pointer itself is not const, even though it points to something that is const).
Something can be const at several levels, e.g. const int *const.
const int is also const at the top level, because there's only one "level" here.
If you have more than one star (e.g. int ***), then the type is top-level const only if const is placed after the rightmost star.
So, const int is const at the top level, meaning const int and int only differ in top-level constness.
But (similarly to const int *) const Foo& is const not at the top-level. It's a non-const reference to const Foo. (The references can never be const1, e.g. Foo &const doesn't compile.)
So the difference between Foo & and const Foo & is not on the top level, making Foo(Foo &) and Foo(const Foo &) different constructors.
1 Some argue that all references are effectively const because you can't make them point to a different object after they're created. But the language says they're not const, and std::is_const_v returns false for them.
There is a fundamental difference between the two.
One is an overload between int and const int. It's a value type. There is no semantic difference for the caller, the effect of const only affects the body of the function.
void f(int);
int a = 1;
const int b = 2;
f(a); // must copy the int value into the argument
f(b); // same thing.
The other is a const vs a mutable reference. It has a difference for the caller.
void f(int&);
void f(const int&);
int a = 1;
const int b = 2;
f(a); // could call f(int&) or f(int const&), but the mutable is a more closely match
f(b); // can only call f(int const&);
Since its passed by reference, the constness matter for the caller of the function. A function that tries to mutate a const object by reference must be invalid, and a non const object should be passed to the non const overload by default.
With only values, it don't matter at all. It is a new object. No matter the qualifier, it has no meaning for the caller, and it should therefore not care since it only affect the implementation.
You can even add const in definitions only if you want, since it declares the same function:
void f(int);
int main() {
f(1);
}
void f(const int a) {
std::cout << "hello " << a << std::endl;
}
Live example
As for your second question, I would say that since the addition of rvalue reference, there is little need for a copy constructor to take by mutable reference.
For example, std::auto_ptr used to have a constructor that took a mutable reference to transfer ownership, but it created all sorts of problems. But it has been completely replaced by std::unique_ptr, which uses rvalue reference to transfer ownership.
Rvalue reference ensure that you don't care for the integrity of the copied-from object, and that it's okay to steal off resources from it.
#1 is a redefinition error because even if you modify the local x it is passed by value so after the return call the value will stay the same.
is it valid if I define member functions with same name¶meters but different return types inside a class like this:
class Test {
public:
int a;
double b;
}
class Foo {
private:
Test t;
public:
inline Test &getTest() {
return t;
}
inline const Test &getTest() const {
return t;
}
}
Which member function gets called if we have following code?
Foo foo; // suppose foo has been initialized
Test& t1 = foo.getTest();
const Test& t2 = foo.getTest();
no, it is not valid, but in your example it is, because the last const is actually part of the signature (the hidden Foo *this first parameter is now const Foo *this).
It is used to access in read-only (get const reference, the method is constant), or write (get non-const reference, the method is not constant)
it's still a good design choice to return the reference of the same entity (constant or non-constant) in both methods of course!
No.
You cannot overload on return type.
Why? The standard says so.
And it actually makes sense - you can't determine what function to call in all situations.
Const and non-const methods with the same formal parameter list can appear side-by-side because the this pointer is treated as a hidden argument and would have a different type. This may be used to provide mutating and non-mutating accessors as in the code in your question.
If the signatures are exactly the same, then no.
To expand upon the previous answers and your given code with an example so you can actually tell what's being called when:
#include <iostream>
class Test {
public:
int a;
double b;
};
class Foo {
private:
Test t;
public:
inline Test &getTest() {
std::cout << "Non const-refrence " << std::endl;
return t;
}
inline const Test &getTest() const {
std::cout << "Const-refrence " << std::endl;
return t;
}
};
int main() {
Foo foo;
Test& t1 = foo.getTest();
const Test& t2 = foo.getTest();
const Foo bar;
const Test& t3 = bar.getTest();
return 0;
}
With output:
Non const-refrence
Non const-refrence
Const-refrence
The const you see after the second getTest signature tells the compiler that no member variables will be modified as a result of calling this function.
is it valid if I define member functions with same name¶meters but different return types [...]?
No.
Neither a method class nor a non-class function.
The reason is ambiguity. There would be situation in which the compiler could not pick the right overloading only by deducing the returned value.
In conclusion: you can't overload methods based on return type.
In your example, those two methods:
Test& getTest();
const Test& getTest() const;
Are correctly overloaded because the signature is different, but not because the return value is different!
Indeed, a function signature is made up of:
function name
cv-qualifiers
parameter types
method qualifier
So the signature of your methods are:
1) getTest();
2) getTest() const;
^------ Note that const qualifiers of the method is part of signature
As you can notice, the return value is not part of signature, but the const of the method qualifier is.
Which member function gets called if we have following code?
With the following code:
Foo foo;
Test& t1 = foo.getTest();
const Test& t2 = foo.getTest();
It will call only the no-const method, even in the case t2.
The reason is that foo object is no-const in that scope, so each
method will be called in its no-const form.
In details, in the third line:
const Test& t2 = foo.getTest();
foo.getTest() will return the no-const reference and after will
be implicitly converted in a const reference.
If you want to force the compiler to call the const version, you should "temporary convert" the object foo in a const.
For example:
const int& t2 = static_cast<const Foo&>(foo).getTest();
In that case I get a const ref to the object, so the object will be treated like a const and the proper const method will be invoked.
Given the code below:
class A
{
public:
A(): value( 0 ) {}
int* get()
{
return &value;
}
const int& get() const
{
return value;
}
private:
int value;
};
int main()
{
A a;
const int& ref_value = a.get();
}
results in the following compilation error:
prog.cpp: In function 'int main()':
prog.cpp:23:35: error: invalid conversion from 'int*' to 'int'
const int& ref_value = a.get();
^
It seems that the overloaded get() method with const modifier does get ignored completely and the compiler sees only the non-const definition of it. It is somehow understandable since the a object is not constant. One solution would be to make the a object constant. Though there are other two solutions that makes the code compileable:
Change the signature of the const get() method by different name or other parameters added.
int* get();
const int& get_changed() const; <-- this gets called
Change the non-const get() method to return a reference instead pointer.
int& get(); <-- this gets called
const int& get() const;
though with
int* get();
const int& get() const;
we have a compiler error.
What puzzles me is the reason behind all of these behavior.
When you have both a const and non-const overload of the same function with the same parameters, which one gets called depends only on the constness of the object on which you're invoking the function. So invoking on a non-const a must call the non-const overload.
It's exactly the same situation as this:
void foo(int *p);
void foo(const int *p);
int main()
{
int i;
const int ci;
foo(&i); // Calls the first overload
foo(&ci); // Calls the second overload
}
A const-qualified function can be called on a non-const-qualified object, but that requires a "nonconst to const" conversion. If there's an overload which doesn't require such a conversion (is a better match), it will be preferred.
The reason is that the compiler can't do overload resolution based on a different return type.
Only the function signature is taken into account for overload resolution. And the function signature consists only of the function name, the parameters, and the cv qualifiers - not the return type.
I am wandering why C++ chooses to call non const methods on non const object for overloaded methods differing only by const method signature, Namely:
#include<iostream>
class Foo
{
public:
Foo() {}
int bar() const { std::cout<<"const version called"<<std::endl; }
int bar() { std::cout<<"version called"<<std::endl; }
};
int main()
{
Foo f;
f.bar();
const Foo g;
g.bar();
return 0;
}
I understand that for g object, being const, the const version of bar is called. But how about f? The output is
version called
const version called
Thanks for your insights.
This is how overload resolution works.
First, a list of candidate functions is constructed. In your case it consists of two functions:
int Foo::bar() const;
int Foo::bar();
Each function in the list is converted to accept this argument roughly like this:
int Foo_bar(const Foo* this);
int Foo_bar(Foo* this);
Then the choice is made according to the "Best viable function" (13.3.3) part of the C++ standard, which says, in short, that the best function is the one requiring the least number of conversions for its arguments. So if you have Foo f; and call f.bar(), the best function is the one accepting non-const this, because it requires no conversions, whereas the const version would require a qualification conversion for this from Foo* to const Foo*.
A non-const object can call both const as well as non-const versions of particular function. Whereas const object can only call const version of that function.
whether it is possible to overload function by const specifier. That is, we have two functions, one constant the other is not, can we say that the constant function overloaded non-const function ?
Yes and No.
It depends on where you put the const specifier.
When defining member functions, this is possible (Yes-part):
int f() { /*code*/ } //invoke this function on non-const object
int f() const { /*code*/ } //ok : invoke this function on const object
Note that in the absence of the first function, even non-const object will invoke the second function (i.e const member function) and in the absence of the second function, you wouldn't be able to invoke the first function on const objects!
But this is not possible (No-part):
int g() { /*code*/ }
const int g() { /*code*/ } //error: redefinition
irrespective of whether they're member functions or free functions.
Per ยง 13.1 / 2:
It's not possible to put const in return-type to overload:
Function declarations that differ only in the return type cannot be
overloaded.
int func();
const int func(); // Error
It's not possible to put const in parameter-list to overload:
Parameter declarations that differ only in the presence or absence of
const and/or volatile are equivalent.
void func(int x);
void func(const int x); // Error
BUT, it's possible:
const and volatile type-specifiers buried within a parameter type
specification are significant and can be used to distinguish
overloaded function declarations.
void func(int &x);
void func(const int &x); // OK
And, it's possible to put const at end of method declaration to distinguish overloads:
int func();
int func() const; // OK