Overloading funtions with const qualifier

M

matthias_k

Hi,

I've never thought about this before, but since you are able to overload
a function only by changing the const-ness of the formal parameters,
some annoying side effects will arise.

For example, if you only have this code ...

void foo( const int n ) {}

int a;
foo( a );

.... the compiler will not complain about the non-constness of a.
This is not surprising, I always treated the const qualifier for a
parameter as some kind of contract between me an d the client that the
actual parameter will never be modified.
However, if you overload foo, like this ...

void foo( int n ) {} // 1
void foo( const int n ) {} // 2

int a;
foo( a ); // 1

int const b;
foor( b ); // 2

.... the compiler suddenly cares about the const-ness of the argument.
So, if you initially have this code:

void foo( const int n ) {}

int a;
foo( a );

and suddenly some coder comes along and overloads foo in the way
mentioned above, a completely different function is called!

Maybe it's not such a good idea afterall to make parameters const in
order to imply non-modifying behavior??

Eager to hear your opinions,
- Matthias
 
A

Andrew Koenig

However, if you overload foo, like this ...

void foo( int n ) {} // 1
void foo( const int n ) {} // 2

This is not overloading; it is two definitions of the same function. So the
compiler should reject it as a multiple definition.

C++ always discards top-level const in parameter types.

Note that in

void foo(int* n);
void foo(const int* n);

the const here is not a top-level const and therefore is not discarded. But
in

void foo(int* n);
void foo(int* const n);

the const *is* a top-level const, so again this last example is two
declarations of the same function.
 
D

dtmoore

matthias_k said:
Hi,

I've never thought about this before, but since you are able to overload
a function only by changing the const-ness of the formal parameters,
some annoying side effects will arise.

For example, if you only have this code ...

void foo( const int n ) {}

int a;
foo( a );

... the compiler will not complain about the non-constness of a.

Of course not ... foo takes its arguments by value, so it copies a to
get the value of n. All the compiler will care about is if you try to
change the value of n within the body of foo.
This is not surprising, I always treated the const qualifier for a
parameter as some kind of contract between me an d the client that the
actual parameter will never be modified.

You should "treat the const qualifier" as a fundamental element of C++
that permeates the entire development process. Deciding which
functions in an interface to make const is of central importance to
designing your software. Assuming of course that you observe proper
const-correctness throughout your software (see below).
However, if you overload foo, like this ...

void foo( int n ) {} // 1
void foo( const int n ) {} // 2

int a;
foo( a ); // 1

int const b;
foor( b ); // 2

... the compiler suddenly cares about the const-ness of the argument.

Nope, wrong. Statements 1 and 2 above represent identical declarations
as far as calling foo is concerned. Since both have definitions, a
Standard Compliant compiler should reject the code.
So, if you initially have this code:

void foo( const int n ) {}

int a;
foo( a );

and suddenly some coder comes along and overloads foo in the way
mentioned above, a completely different function is called!

No (see above).
Maybe it's not such a good idea afterall to make parameters const in
order to imply non-modifying behavior??

Eager to hear your opinions,
- Matthias


Proper understanding of how to use const is essential to correct
programming in C++, and it seems like maybe you have some pretty deep
misunderstandings about it. I suggest reviewing a good book, such as
Herb Sutter's "Exceptional C++" or Scott Meyer's "Effective C++". At
least you should look at: http://www.gotw.ca/gotw/006.htm
HTH,

Dave Moore
 
J

Jonathan Turkanis

dtmoore said:
Proper understanding of how to use const is essential to correct
programming in C++, and it seems like maybe you have some pretty deep
misunderstandings about it. I suggest reviewing a good book, such as
Herb Sutter's "Exceptional C++" or Scott Meyer's "Effective C++". At
least you should look at: http://www.gotw.ca/gotw/006.htm
HTH,

I can't argue with your suggested reading list, but I think your diagnosis may
be overly pessimistic. :) It looks like the OP was confused because he didn't
know the rules for parameter type decay from 8.3.5/3, which are important but a
bit obscure.

Jonathan
 
E

E. Robert Tisdale

Andrew said:
This is not overloading; it is two definitions of the same function.
So the compiler should reject it as a multiple definition.

C++ always discards top-level const in parameter types.

Note that in

void foo(int* p);
void foo(const int* p);

the const here is not a top-level const and therefore is not discarded.
cat foo.cc
#include <iostream>

void foo( int* p) { // 1
std::clog << "foo(int*)" << std::endl;
}

void foo(const int* p) { // 2
std::clog << "foo(const int*)" << std::endl;
}

int main() {
const
int m = 0;
int n = 0;
foo(&m);
foo(&n);
return 0;
}
g++ -Wall -ansi -pedantic -o foo foo.cc
./foo
foo(const int*)
foo(int*)

This seems to suggest that,
if you define foo(const int*), you should also define

inline
void foo( int* p) {
foo((const int*)p);
}

to prevent the "client" from overloading foo in this way.
 
M

matthias_k

Andrew said:
This is not overloading; it is two definitions of the same function. So the
compiler should reject it as a multiple definition.

Hm, I didn't try to compile this example (since I pulled it out of my
hat), but I had a very similar problem which lead me to this question
and which *did* compile. However, in that last case the two functions
differed in their return types:

inline std::string str_to_lower ( const std::string& source )
{
std::string target;
std::transform( source.begin(), source.end(),
std::back_inserter(target), to_lower );
return target;
}

inline void str_to_lower ( std::string& source )
{
std::transform( source.begin(), source.end(), source.begin(),
to_lower );
}

Those two functions convert an std::string to lower case, but the first
is non-modifying and the second is modifying. The compiler didn't complain.

Regards,
Matthias
 
M

matthias_k

dtmoore said:
No (see above).

So what would happen if I would call foo on references?
void foo( int& n );
void foo( const int& n );
Proper understanding of how to use const is essential to correct
programming in C++, and it seems like maybe you have some pretty deep
misunderstandings about it. I suggest reviewing a good book, such as
Herb Sutter's "Exceptional C++" or Scott Meyer's "Effective C++". At
least you should look at: http://www.gotw.ca/gotw/006.htm
HTH,

I have read both Effective books by Scott Meyers and I recently bought
the third one on STL. I don't see though what exactly I am
misunderstanding about the keyword const. Maybe you could just tell me?
Dave Moore

Thanks,
Matthias
 
M

matthias_k

Jonathan said:
dtmoore wrote:




I can't argue with your suggested reading list, but I think your diagnosis may
be overly pessimistic. :) It looks like the OP was confused because he didn't
know the rules for parameter type decay from 8.3.5/3, which are important but a
bit obscure.

Jonathan

Hi Jonathan,

unfortunately I don't have a copy of the standard, what is this passage
about exactly?

Thanks,
Matthias
 
D

Dave Moore

matthias_k said:
So what would happen if I would call foo on references?
void foo( int& n );
void foo( const int& n );

Well, obviously that can make a big difference, because now you have
redefined foo for pass by reference, and so the effective scope of the
parameter is different. That is, any changes you make through the reference
parameter will now be reflected outside the scope of foo. Also, your
compiler will not complain, because you (or someone else) have now done a
legal overload of foo.

This is an altogether different situation from the example in your different
post. While it is completely normal to have const and non-const versions of
member functions (which implicitly take const or non-const references to the
object they are called for), I cannot immediately see a legitimate reason to
overload non-member functions in this way (although that doesn't mean there
isn't one). If you are worried about "accidental" overloading, the best way
to handle that is probably to put the function inside a namespace. You
could also define your own non-const version of foo simply as:
void foo(int &n) {
foo(const_cast<const int&>(n));
}
Note that this is a "good" use of const_cast, since you are casting *to* the
const type. This will then generate a compile-time error if someone tries
to overload it.

If you are worried about "malicious" overloading, then I have no useful
suggestions ... dedicated hackers will probably be able to defeat anything I
can devise.
I have read both Effective books by Scott Meyers and I recently bought
the third one on STL. I don't see though what exactly I am
misunderstanding about the keyword const. Maybe you could just tell me?

Maybe you have no misunderstandings concerning const... I may have read too
much between the lines from your first post. If so, then please accept my
apologies.

HTH,

Dave Moore
 
M

matthias_k

Dave said:
This is an altogether different situation from the example in your different
post. While it is completely normal to have const and non-const versions of
member functions (which implicitly take const or non-const references to the
object they are called for), I cannot immediately see a legitimate reason to
overload non-member functions in this way (although that doesn't mean there
isn't one).
Yes, my example wasn't too good, I should have posted the original code
from the beginning. Please see my other answer where I also posted the
code of the two functions from which my problem arose.
They are basically two versions of the same function, one which is
modifying, the other being non-modifying.

If you are worried about "accidental" overloading, the best way
to handle that is probably to put the function inside a namespace. You
could also define your own non-const version of foo simply as:
void foo(int &n) {
foo(const_cast<const int&>(n));
}
Note that this is a "good" use of const_cast, since you are casting *to* the
const type. This will then generate a compile-time error if someone tries
to overload it.

The problem is that, in my case, I want to have both versions of the
function (see other post). They are both in the same namespace. If I now
want to explicitly call the non-modifying version, I have to say:

const std::string src( "WHATEVER" );
std::string ret = str_to_lower( src ); // call the non-modifying version

If I remove the const qualifier from src, this piece of code won't
compile. It would however, if I had only the non-modifying version of
the function. That's the dilemma I was talking about. In my case this
isn't too much of a problem because I have written both functions and I
know how to use them, but this may not always be the case.

Regards,
Matthias
 
D

Dave Moore

matthias_k said:
Yes, my example wasn't too good, I should have posted the original code
from the beginning. Please see my other answer where I also posted the
code of the two functions from which my problem arose.
They are basically two versions of the same function, one which is
modifying, the other being non-modifying.

If you are worried about "accidental" overloading, the best way

The problem is that, in my case, I want to have both versions of the
function (see other post). They are both in the same namespace.

Well, I think you should seriously re-examine your need to have both
versions with the same name. The whole point of overloading is to clarify,
not obfuscate. What would be lost if you were to change the name of one
version of the function?
If I now
want to explicitly call the non-modifying version, I have to say:

const std::string src( "WHATEVER" );
std::string ret = str_to_lower( src ); // call the non-modifying version

Ok .. so here you have a workaround if you need one .. you could also use
the const_cast technique from my earlier post. However, I agree that both
are unwieldy? So again, why do you want two versions of the "same" function
that behave differently?

Dave Moore


Dave Moore
 
M

msalters

matthias_k said:
What does "top-level const" mean?

const can appear in a number of positions when naming a type,
e.g. int const* or int * const [ or int const * const ]. For a type T,
at most one of the const's in T applies to the sizeof(T) bytes of
the object itself. This const, if present is called the top-most
const. In the examples, int * const has a top-level const (it's
a const pointer to a non-const int) while int const* doesn't
(the pointer can be modified, but not the int's pointed to.

In practice, it often is the right-most const, but there are
counter-examples:

void (*const functionptr) ( int const* arg ) = &foo;

The second const refers to arg. The first const actually makes
this a constant pointer.

Regards,
Michiel Salters
 
M

Matthias

Well, I think you should seriously re-examine your need to have both
versions with the same name. The whole point of overloading is to clarify,
not obfuscate. What would be lost if you were to change the name of one
version of the function?

That is true of course. I just thought that it may be easier for the
client to just remember one name :)
But in this case, it's indeed better to name them differently, maybe
str_to_lower_const and str_to_lower or such.
The point was just that this was a situation I had not encountered
before, so I thought I'd better ask here how to handle this properly.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,202
Latest member
MikoOslo

Latest Threads

Top