Link to home
Start Free TrialLog in
Avatar of rats54
rats54

asked on

Function Parameters

1. void newfun(int const &argcm) {}
2. void newfun(const int &argcm) {}
3. void newfun(const char** &argvm){}
4. void newfun(char** const &argvm){}
5. void newfun(const char**const &argvm){}

I was trying to find out that although 1 and 2 are same, 3 and 4 are not.In 1 and 2 i can pass int or const int, but in 3 I need to pass a const char ** only, where as 4 cannot take const char ** but only char **. The behaviour inside the functions remains same however in all the cases.
And what does 5 mean?
Avatar of Hoegje
Hoegje

>>
1. void newfun(int const &argcm) {}
2. void newfun(const int &argcm) {}
3. void newfun(const char** &argvm){}
4. void newfun(char** const &argvm){}
5. void newfun(const char**const &argvm){}

I was trying to find out that although 1 and 2 are same, 3 and 4 are not.In 1 and 2 i can pass int or const int, but in 3 I need to pass a const char ** only, where as 4 cannot take const char ** but only char **. The behaviour inside the functions remains same however in all the cases.
And what does 5 mean?
<<

All of them : the '&' sign means that you pass the variable by reference, which means that there won't be made any copy of the actual parameter when calling the function. When you don't use the & sign, you can have some strange behaviour.
eg. :
void func1(int j){
  j++;
}

void func2(int& j){
  j++;
}

int main(){
  int a = 1;
  int b = 1;
  func1(a);
  func2(b);
  // here a equals 1 because on a call from func1 with parameter a, the compiler will make a copy from a to j, so j will take the value 1 inside the function func1
  // but b equals 2 in main, because you pass func2 a REFERENCE to an integer, which means the compiler will not try to copy the actual parameter (b) to j, but instead will give (the adress of) the variable b itself, and increment that value directly.
}

So that is &.

Now on to your examples :

1 and 2. in 1 you have a constant reference to an integer, which means you can't change the reference (to point to some other element) but you CAN change the integer itself (its value). In 2 you could change the reference, but can't change the integer value. One reads nr 2 as a reference to a constant integer. So I would not call them exactly the same.

3 vs 4. Same thing here (read backwards) : 3. pointer to a pointer to a char that is const. A char* is normally referred to as a c-string, so this read : pointer to a const string.
4. const pointer to a string => so can't repoint the pointer, but you could change the string.

5. is all const, so no changing of pointer nor the string.

>> 1 and 2. in 1 you have a constant reference to an integer, which means
>> you can't change the reference (to point to some other element) but you
>> CAN change the integer itself (its value).
Wrong.  You can never change a reference to "point" to something else.

1. void newfun(int const &argcm) {}
2. void newfun(const int &argcm) {}

In both of these cases the parameter is a reference to a constant integer.  This means the procedure is passed a reference to the caller's integer, not a copy of the caller's integer.  The difference between the two is not usually significant, but I will show you an example where it is signficant, in a bit.  Since the integer in question is a constant, it can't be changed by this procedure.

3. void newfun(const char** &argvm){}

In this case, the function is passed a reference to a pointer.  that pointer points to a pointer to constant characters.  the two pointers in question can be changed, but the characters they point to cannot.

           4. void newfun(char** const &argvm){}

The function is passed a reference to a constant pointer.  This pointer points to a pointer to non-constant characters.  You can change the characters through these pointers, but you cannot change the constant pointer.

           5. void newfun(const char**const &argvm){}

The function is passed a reference to a constant pointer.  this pointer points to a pointer that points to constant characters.  Neither the characters nor the constant pointer can be changed.  The non-constant pointer can be changed.

continues
lets first talk about the reference stuff, which is an unnecessary complication in all of this.

a reference is a lot like a pointer.  It means the variable refers to another variable, like a pointer does.  However you can never change the reference to refer to anything other than what it was initialized to refer to.  also you never need to use pointer-like syntax (* or ->) with a reference variable, instead it is used syntacically just as if it was the variable it referes to.   Here are some examples.

int i = 8;
int &IRef = i;

cout << IRef   ; // Will print 8, since i is 8.
i = 5;
cout << IRef   ; // Will print 5, since i is 5.
IReg = 2;      // Changes i to 2
cout << i  // Will print 2.

continues
non-constant references are often used in functions as a way to let functions return information to the caller, like

Increment(int &I)
{
   ++i;  // Increment the integer referenced.
}

int main()
{
   int x = 0;
   Increment(x);
   cout << x ;    // Prints 1, because the change made in increment,
                       // was made to the referenced integer.
}

constant references are often used in functions as a way to pass information to a function, without forcing the data to be copied.  This can be important when passing an object of a class that has a slow copy-constructor, or when an object is polyporphic and copying it woudl "slice" it.  for example.

SomeFunction(const vector<int> &V);
{
   cout << V[0];  // Print the first item in the vector.
}

in this case, the function acts like

SomeFunction(const vector<int> V);

howevern when it is called, the vector passed to the function does not have to be copied, which can be a time-consuming operation.

continues

Now lets talk about "const"   "const" indicates that an object is constant.  The item in question is the item to the left of the const keyword.  However, if there is no item to the left, then it applies to the right.  so for example

int const i;
const int i;

both define a constant integer.  The 2nd case, the one you will see programmers write most often, is actually the "exceptiuon" case, since there is nothing to the left for the const to apply to, so it applies to the right.

Now

int x;
int const &i = x
const int &i = x;

both define references to intgers and those integers must be treated as constants.  In this case the integer refered to is x and it is not constant.  You can change the value of x since it is not constant, like

x = 3;

and i will then seem to be 3.   However i acts like a constant integer so you cannot do

i = 3;  

since that would be changing a constant.
1. void newfun(int const &argcm) {}

const int reference - a reference to a constant int.

2. void newfun(const int &argcm) {}

same.

3. void newfun(const char** &argvm){}

a reference to a pointer to a pointer to a const char.

4. void newfun(char** const &argvm){}

a const reference to a pointer to a pointer to a (non-const) char.

5. void newfun(const char**const &argvm){}

a const reference to a pointer to a pointer to a const char.

So yes, 1 and 2 are the same while 3, 4, and 5 are all different.

a const reference to T is usually the same as a plain T if T is a small type and is a true reference if T is a bigger type (this is of course compiler specific but compilers are at liberty to treat a const T & as a T whenever that is safe to do so and in practice they will do it whenever T has no user defined constructor and sizoef(T) <= some value - often sizeof(double)).

since a pointer is always of 'small size' and never has a user defined constructor it virtually never makes sense to manually declare a type to be a const reference to a pointer. It is typically the result of template generations though, but manually there's no reason to declare a type like that. 'T  * const &' can always be changed to just 'T *' for any type T.

with this in mind I would do the following 'cleanup' to the declarations:

1. void newfun(int const &argcm) {}

void newfun(int argcm) {}

If you really want to avoid changing the argument inside newfun you can declare it 'const int argcm'. Under no circumstance will the function change the original argument anyway.

2. void newfun(const int &argcm) {}

same, since this really is the same as 1.

3. void newfun(const char** &argvm){}

void newfun(const char ** & argvm) {}

Note that here the reference actually make sense since it is non-const and you might want newfun to change the pointer to the pointer variable. However, this is most likely a bug since a pointer to a pointer usually already point to a table and you rarely want to change that pointer. It probably should have been a const reference so it really is situation 4. It isn't always though and in the few cases where you really want to have the ability to change the pointer to the pointer this is the correct declaration for it.

4. void newfun(char** const &argvm){}

void newfun(char ** argvm) {}

If you really want to make sure that the body of newfun doesn't modify the local pointer to the pointer you can declare it:

void newfun(char ** const argvm) {}

but if you can't ensure that the function newfun doesn't modify the local variable by simply seeing it immediately the function body is probably too big :-) In any case if you did modify argvm it would only change the parameter and not the value given in the function call, that value would be untouched. The data pointed to is a different matter, they are not const and so you can freely modify those and that will be seen by the caller as well.

5. void newfun(const char**const &argvm){}

void newfun(const char ** argvm) {}

Note that this disallows you to do argv[1][2] = blah blah but it does NOT disallow you to do argv[1] = blah blah;

You might want to consider using:

void newfun(const char * const * argvm) {}

This will disallow you to do argv[1] = blah blah; as well.

argvm can be changed but that won't affect caller, the other two changes argv[1] = ...; and argv[1][2] = ...; will and so it makes more sense to make both those const than it is to make a const reference on top of all this.

There are cases when you want to manually write const T & and therefore also things like char * const &. But never by itself. It has to do with templates since a template typically declare value parameters as 'const T &' and if T has the value 'char *' this becomes 'char * const &' (it does NOT become 'const char * &', some people make that wrong assumption).

If you therefore have a function that is a specialization of a template you will typically end up writing your parameter with the const reference argument.

Of course, the reason why you do that in template arguments is the case that T is a big class or struct (sizeof(T) is a large value) or T has some constructor. Declaring const T & avoids the call to a constructor and also avoid copying the object (if it is large) and simply passes a reference to it (actually a pointer).

Thus it also makes sense to do:

class X {
private:
   int x;
   int y;
   double z;
   char buf[100];
   // a big class object...

public:
   // constructor...
   X();
   X(const X & x);
};

void newfun(const X & x);

if you did:

void newfun(X x);

you would create a new x object when you call the function and that was most likely not what you wanted, partly because it is big, partly because it has a constructor and that would mean you would invoke a constructor and partly because such big objects are often unique in a program. You have an object that represent some "real world object" such as a person, user or window on the screen or what have you. You don't want the system to create duplicates of this object in many places, you want to keep only one object and any modifications is done to that single object that represent that particular person, user or window. Thus a reference or pointer is the obvious candidate.

Also, note that copy constructors demand a const reference since you otherwise would have endless recursion.

Also, note that const references similarly will occur in pointers to functions since a function pointer doesn't convert to another function pointer with respect to recursive conversion of the arguments. Even though there's an implicite conversion of 'char' to 'int' a function pointer such as 'void (*)(char x)' will not convert to a function pointer 'void (*)(int x)' and so if you have a function pointer with a function argument of 'char * const & p)' then you have to declare that exact prototype when you define a function that is to be stored into that pointer.

Hope this explains the matter.

Alf
>> 1 and 2. in 1 you have a constant reference to an integer, which means
>> you can't change the reference (to point to some other element) but you
>> CAN change the integer itself (its value).
Wrong.  You can never change a reference to "point" to something else.


That is what it said !!

>>In 2 you could change the reference, but can't change the integer value
<<

See the difference in verbs : could vs can. In theory, you could change the reference, because it is not const (in 2), but because it is a reference, it is not possible.

You are saying exactly what I am saying : things that are const can't be changed, things that are NOT const can be changed in theory, except that eg. references can't be changed when used as a parameter.
Now pointers

int *p;

means p is a pointer that can point to a non-constant integer.  Note that it can't point to a constant integer since this pointer can be used to change the value of what it points to.  For example.

int ni;
const int ci;

int *p = &ni; // okay
*p = 3;  // changes ni, that's  okay.

p = &ci;  // NOT ALLOWED
*p = 3; // This would changed ci, not allowed.

const int *p

means p is a pointer that can point to a constant integer.  Nowever, it could also point to a non-constant integer, but as far as the pointer is concerned, the integer is constant.  For example.

int ni;
const int ci;

const int *p = ci; // okay.
p = ni; // okay too.
*p= 3; // Not allowed no matter what p points to.  p points to a constant.
ni = 3; // this is still allowed, by you can't make the change through p.

Now

int const *p

is the same as

const int *p

remember that "const" applies to the thing on the left, if there is one

continues
You said :

1. void newfun(int const &argcm) {}

const int reference - a reference to a constant int.

4. void newfun(char** const &argvm){}

a const reference to a pointer to a pointer to a (non-const) char.

I say :

waht is the difference, there isn't any. The first is a const reference to an integer, just as the second is a const reference to a pointer to a pointer to a char.

if T is any type (or class):
T const &argument = a const reference to a (non-const) type

vs.

const T &argument = a (non-const) reference to a const type.

You say 2 different things for the same syntactic sentence.
Now

int *const p;

means that p is a pointer to a non-constant integer (like int *p), but in this case the pointer cannot be changed.  For example

int i;
int j;

int *const p = &i;

*p = 3; // Okay to change i with p, it doesn't point to a constant.
p = &j;  // ERROR p is constant, you can't change p.

now

const int *const p
int const *const p

mean that p is pointer to a constant integer and the pointer itself is constant.  i.e. you can't change the pointer to point to something else and you can't change the value the pointer points to.

from these "definition" you can then build up a description of any pointer ot reference expression

>> That is what it said !!
sorry, I copied the wrong sentince.  You said

>> 1 and 2. in 1 you have a constant reference to an integer, which means
>> you can't change the reference (to point to some other element) but you
>> CAN change the integer itself (its value). In 2 you could change the reference,
>> but can't change the integer value
In 2 you CANNOT change the reference.  that is wrong.  you can never change a reference.

>> except that eg. references can't be changed when used as a parameter.
No.  references can't be changed.  period.

>> waht is the difference, there isn't any. The first is a const
>> reference to an integer, just as the second is a const
>> reference to a pointer to a pointer to a char.
The differences are numerious????
>> waht is the difference, there isn't any. The first is a
>> const reference to an integer, just as the second is a
>> const reference to a pointer to a pointer to a char.
You keep saying "constant reference" as if the "const" in

int const &i

makes the reference constant.  it doesn't references are always constant.  What it does is makes the int constant.  So when you say "constant reference to an integer" it is really a "reference to a constant integer"   You have the constant applied wrong.
wowowowow,

lots of mistakes here.

const int & arg

The 'const' here does NOT mean you can't change the reference to point to something else. You can NEVER change a reference to point to somethhing else, the const has NOTHING to do with it.

The const refer to the 'int' and means you cannot modify the int referenced by the reference.

int & arg

you can't change the reference here either:

arg = x;

will modify the integer that arg references, it will NOT change arg in any way. References are ALWAYS const by their very definition. You can initialize them but never assign to them.

Another thing. References are usually implemented as pointers. This means that:

struct X {
   int & p;
   int k;
};

This struct will have the same size as a struct declared as:

struct Y {
   int * p;
   int k;
};

Another thing is that the struct X won't work very well, if you have a reference you must initialize that reference and that means the struct X must have a constructor, so let's add a constructor to it:

X::X(int & q, int j)
{
   p = q;
   k = j;
}

This will NOT work. The reason is that you are trying to assign to a reference and I just said you can't assign to references, only initialize them. Therefore you must put the initialization of p in the constructors initialization list:

X::X(int & q, int j)
  : p(q), k(j)
{}

This will work, you could even have placed k = j; as in the previous attempt and it would also work but it is important to be aware of that the 'k = j;' and the k(j) does two different things. The k(j) thing means that you initialize the member k with the value j in 'k = j;' you assign the value j to the member k. Assignment and initialization are two very different things in C++. Assignment involves the operator = (...) for proper overload while initialization involves the constructor for proper overload and never calls the assignment operator unless you do so yourself inside the constructor.

However, while a reference is implemented using a pointer it acts like a non-pointer. You use a T & variable as you use a T variable it's just that whenever you say 'var' the compiler treat it as if you had written '(*var)' and when you say '& var' it treat it as if you had written 'var'. when you write 'var.foo' the compiler read it as if you had written 'var -> foo' etc.

Now a const T & means a reference that you cannot change, for all practical purposes this behaves the same as a variable of type 'T' when it is an argument to a function. The difference is that 'T' require a constructor and if the type T is large it will take up much space on the stack making that local variable while 'const T &' does not, it just tells the caller to pass the address to a T object.

For the function itself though, there is no difference. Yes the compiler treat a 'var' as '(*var)' etc as indicated above but that is all handled by the compiler so the programmer doesn't see any difference between 'T var' and 'const T & var' in the argument list. For this reason many templates uses 'const T &' as argument since T can be any type and the type is of unknown size. However, if T happen to be a small simple type it is cheaper to just send off a copy of the value rather than sending the address of the value. Since the type is 'const T &' the caller know that the function won't modify the data and so it can choose to instead send a copy of the value in this case and so treat 'const T &' as plain 'T'. Since it is the compiler that makes this decision and it is also the compiler that handles the translation of 'var' to '(*var)' etc it is a simple matter for the compiler to ensure that it only treat 'var' as '(*var)' if it truly is passed as a reference and if the compiler decides to send a copy of the value it won't do it that way after all.

This allow you to write a template using 'const T &' and if the type is a small type the value is sent by value as a plain 'T' but if the type T is large or the type has a constructor that need to be called if you construct a new value it will instead just take the address of the value and send that instead and thus the 'const T &' really is a reference in this case and is actually handled by the function receiving a pointer to the object 'const T *' and when the program code refer to 'var' the compiler handle it as if it was '(*var)' etc as indicated above.

Another note, it is important to read these const things correctly:

T x;

a variable of type T

const T x;

a const T variable x. You can't modify x.

T * x;

a pointer to T named x.

const T * x;

a pointer to const T, you can change the pointer variable but you can't change the data it points to.

T * const x;

a const pointer to T named x. You can't change the pointer variable but you can change the data it points to.

const T * const x;

a const pointer to const T anmed x. You can't change the pointer variable and you can't change the data it points to.

T ** x;

a pointer to pointer of type T.

T * const * x;

a pointer to const pointer to T. You can change the pointer, you cannot change the data the pointer is pointing to but you can change the data the pointer's pointer is pointing to.

x = foo; // ok
*x = bar; // not ok.
**x = baz; // ok.

const T * const * x;

a pointer to const pointer to const T. You can change the pointer but nothing else.

x = foo; // ok
*x = bar; // not ok
**x = baz; // not ok.

Etc.

References are just like pointers in this respect, of course, since you can't have a pointer to a reference (it wouldn't make sense) the reference has to be the last if present. Also, since you can never assign to a reference and only initialize it it never makes sense to declare the reference to be const.

T & const x;

doesn't make sense and C++ won't allow it.

Hope this clear up.

Alf
ASKER CERTIFIED SOLUTION
Avatar of nietod
nietod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Nietod,

Nietod say I said:

>> a pointer to const T, you can change the pointer variable but you can't
>> change the data it points to.
>> 
>> T * const x;

Nietod said:
No!   How can you make that mistake?

That is a constant pointer to a non-constant T.  You can't change the pointer to point to something else.  But you can change the value of the thing it points to.

I say:
Hmm..I see you grouped them wrong, just above the text 'a pointer to const T' you'll see:

const T * x;

and that is what that text refers to.

What you showed is T * const x which is a const pointer to a non-const data T as I also said if you read what comes just below the part you clipped.

About the 'const T &' to 'T' thing, I'll discuss that later, right now I gotta rush. I know it sounds lame but I really don't have time right now, my wife wants me to leav ethe computer right now. I will get back to you on this probably before 48 hours from now.

Alf
>>I see you grouped them wrong
oops.  I knew you wouldn't make that mistake too.
back again,

I promised I would get back to you on that 'const T &' thing.

Hmm..It is possible I am wrong on that issue then. However, this opens up a new can of worms. Consider a function that operate on 'int', let's say you write an int stack class:

class intstack {
private:
   ....
public:
   void push(int v);
};

Now, if you make this a templated class:

template <class T>
class stack {
private:
   .....
public:
   void push(T v);
};

The problem here is that the original function push used data type int which is not problematic as a parameter, a generic type T may have constructor or just be plain too big to use as argument by value and so you want it to be:

template <class T>
class stack {
private:
  ....
public:
   void push(const T & v);
};

The problem here is then that if you then instantiate this class with T = int you don't get int v as argument but you get const int & v.

If this is transferred as argument as a true reference parameter (pointer to an int value) then you can't simply send off the value v, but you must first store it into memory some place and then send off the address of that location as parameter to push. This seems not very optimal and so you may end up with:

template <class T, class parT = const T &>
class stack {
  ....
public:
   void push(parT v);
};

Now you can instantiate the T as int by:

stack<int,int> s1

while:

stack<a_big_type> s2;
stack<type_with_constructor> s2;

This is rather inelegant if you ask me though.

Alf
>> The problem here is then that if you then instantiate this
>> class with T = int you don't get int v as argument but you
>> get const int & v.
Why is this a problem??

>> If this is transferred as argument as a true reference parameter
>> (pointer to an int value) then you can't simply send off the value v,
>> but you must first store it into memory some place and then send
>> off the address of that location as parameter to push.
Assuming the compiler works the way that all seem to work, even though they don't have to, Yes, that is exactly what will happen.  I don't see a problem with that.  I don't see it having anythign to do with templates either.  If you write

somefunction(const int &i)

the compiler is going to end up passing a pointe to an integer, rather than a copy of the integer.  This is not very efficient, but hoestly on MHz machines, who cares.  

And as for "store it into memory some place" In most cases the integer is already stored somewhere, you are just passing the address of that location.  The only exception might be if you pass a literal, like

somefunction(5);

in that case the compiler will probably push a 5 onto the stack to create a temporary int and then invoke the function and at that time pass a pointer to the temporary int.  

Also note that in that case it is neccessary that the reference be constant.  You cannot convert a tempotary to a non-constant reference, like in

somefunction(int &I);

somefunction(5);
>> The problem here is then that if you then instantiate this
>> class with T = int you don't get int v as argument but you
>> get const int & v.
Why is this a problem??



It's a problem of efficiency.

if const int & v is such that references are implemented as pointers (a reasonable assumption) this is - apart from syntax - equivalent to:

const int * v

The result is that when you want to get the value of the integer you get an extra indirection.

For a large type that is good since although you get an extra indirection you would have more code just to copy the value around on the stack but for a small type such as int or char it is more work to handle that extra pointer than it would be to just handle the value directly.

Also, what you say about non-const references and that you cannot pass literal values to them is also worth noting. I believe it is explicitely stated that it is only literal values or constant values that can be passed off as const references. Non-const references must refer to non-temporary variables, since otherwise any side effects won't show up as they should. I believe we are in agreement at this point.

I do believe that even for modern fast machines things can go slow if you go in a loop and handle many items etc and that things like this - as a language feature - should have an eye on not making things slower than it has to be. Part of the success of C and therefore also C++ is exactly that things are 'close to the machine' and that you don't have very expensive layer between the programmer and the hardware.

The reason why I dragged in templates is of course that if you did not write templates you would probably not declare the parameter to be 'const int & v' to a function but would rather just write 'int v'.

Alf
>> It's a problem of efficiency.
Yes, but you woudl have a hard time measuring it

>> I do believe that even for modern fast machines
>> things can go slow if you go in a loop and handle
>> many items
Probably almost never measurably.  If you think about the 80 20 rule, which is much closer to 99-1 in modern GUI app, then most occurances of this simply aren't going to matter because they run in the 80% or so and thus occur  too infrequently.  The few that do occur in commonly running code, sill need to occur alot of the time in that code to matter, like a loop as you said.  But how often do you think there is an occastion where a loop is running that A) passes a small variable (POD) by reference and B) The remaining work of the loop does not take considerably more time the extra indirection (80-20 rule agian).

>> The reason why I dragged in templates
The boost library has a mechanism, I believe it is a traits class, where it can chose the most efficient (at least most likely to be most efficient) way to pass a variable.   I don't remember the details.  So if you are really concerned about this, there is a solution, but I suspect that there almost never any cases where it could matter, not in s real application anyways.