Acceptable to "const" a parameter in the definition but not in thedeclaration?

J

jl_post

Hi,

I'm a big fan of the "const" keyword for several reasons. I don't
want to get too deep in my reasons, but one place I'll use them is in
code like this:

const int numToUse = getSomeNumber(...);

Since I declared numToUse as const, it's clear to the maintainer that
numToUse will not change for the rest of its scope, nor is it supposed
to change (preventing a maintainer from accidentally changing it).

At any rate, sometimes I create a function/method that has a
signature like the following:

void f(int arg1);

But for my example, arg1 is essentially a constant, since it never
changes. So I COULD declare f() as:

void f(const int arg1);

but that const is basically unneeded for the maintainer who only needs
to know the signature of f(). However, putting the const keyword in
tells whoever looks at the definition of f() that arg1 is never
supposed to change.

(Note that I am not passing by reference -- only by value.)

So the advantage of putting const in is that the maintainer who
reads the definition of f() can tell that arg1 is never supposed to
change in f(). But this information is unimportant to the maintainer
who treats f() as a "black-box" -- to him/her it makes no difference
if arg1 (which is passed by value) is changed in f(), so seeing arg1
declared as const is of no help at all.

Playing around with this, I discovered that I can declare arg1 as
const in the definition, like this:

void f(const int arg1) { ... }

while not declaring arg1 as const in the declaration/signature/
prototype, like this:

void f(int arg1);

The compiler will compile this just fine, despite that "const" is not
in both lines. (I tried this on Visual C++, g++ on Win32, and g++ on
Linux.)

If you'd like to see a full working program that illustrates what
I'm talking about, here is one:


#include <iostream>

void f(int arg1);

int main(int argc, char ** argv)
{
f(77);

return 0;
}

void f(const int arg1)
{
std::cout << "arg1 = " << arg1 << std::endl;
}


So my question is: Is this an acceptable thing to do (as far as the
C++ language specifications are concerned)?

(Just because it works on all compilers I've tried it on so far
doesn't necessarily mean it's allowed in the standard language, so I'd
like to know for sure if it's a legal thing to do.)

Being able to do this is good in that the maintainers of the
function can see what passed-in arguments are never supposed to
change, while the other maintainers can treat the function as a black-
box and not need to see which passed-in-by-value arguments are const.

Thanks.

-- Jean-Luc
 
V

Victor Bazarov

I'm a big fan of the "const" keyword for several reasons. I don't
want to get too deep in my reasons, but one place I'll use them is in
code like this:

const int numToUse = getSomeNumber(...);

Since I declared numToUse as const, it's clear to the maintainer that
numToUse will not change for the rest of its scope, nor is it supposed
to change (preventing a maintainer from accidentally changing it).

At any rate, sometimes I create a function/method that has a
signature like the following:

void f(int arg1);

But for my example, arg1 is essentially a constant, since it never
changes. So I COULD declare f() as:

void f(const int arg1);

but that const is basically unneeded for the maintainer who only needs
to know the signature of f(). However, putting the const keyword in
tells whoever looks at the definition of f() that arg1 is never
supposed to change.

(Note that I am not passing by reference -- only by value.)

So the advantage of putting const in is that the maintainer who
reads the definition of f() can tell that arg1 is never supposed to
change in f(). But this information is unimportant to the maintainer
who treats f() as a "black-box" -- to him/her it makes no difference
if arg1 (which is passed by value) is changed in f(), so seeing arg1
declared as const is of no help at all.

Playing around with this, I discovered that I can declare arg1 as
const in the definition, like this:

void f(const int arg1) { ... }

while not declaring arg1 as const in the declaration/signature/
prototype, like this:

void f(int arg1);

The compiler will compile this just fine, despite that "const" is not
in both lines. (I tried this on Visual C++, g++ on Win32, and g++ on
Linux.)

If you'd like to see a full working program that illustrates what
I'm talking about, here is one:


#include<iostream>

void f(int arg1);

int main(int argc, char ** argv)
{
f(77);

return 0;
}

void f(const int arg1)
{
std::cout<< "arg1 = "<< arg1<< std::endl;
}


So my question is: Is this an acceptable thing to do (as far as the
C++ language specifications are concerned)?

What is? The top-level const qualifiers for arguments or return value
are ignored and do not contribute to the function type, so

void f(int);

and

void f(int const);

are two declarations of the same function.

As to acceptability, do what your coding standard tells you. In our
group if the function is declared in some way, you should be able to
look for the declaration using textual search, which means we keep even
the names of the arguments the same...
(Just because it works on all compilers I've tried it on so far
doesn't necessarily mean it's allowed in the standard language, so I'd
like to know for sure if it's a legal thing to do.)
Yes.

Being able to do this is good in that the maintainers of the
function can see what passed-in arguments are never supposed to
change, while the other maintainers can treat the function as a black-
box and not need to see which passed-in-by-value arguments are const.

In our neck of the woods we say, whatever floats your boat.

V
 
A

Alf P. Steinbach

* (e-mail address removed), on 08.06.2010 17:48:
#include<iostream>

void f(int arg1);

int main(int argc, char ** argv)
{
f(77);

return 0;
}

void f(const int arg1)
{
std::cout<< "arg1 = "<< arg1<< std::endl;
}


So my question is: Is this an acceptable thing to do (as far as the
C++ language specifications are concerned)?

It's standard-conforming, yes.

However, only top level 'const' is disregarded for forming the function's type.

E.g. 'int const' -> 'int', but not 'int const*' -> 'int*'.


Cheers & hth.,

- Alf
 
J

Jorgen Grahn

.
However, only top level 'const' is disregarded for forming
the function's type.

E.g. 'int const' -> 'int', but not 'int const*' -> 'int*'.

Which is a slightly convoluted way of saying "only the things that are
surely irrelevant to the interface are disregarded in the interface".

When you use call-by-value, you couldn't care less if the callee
modifies his copy of the value. Easy to remember, and makes good
sense.

/Jorgen
 
A

Alf P. Steinbach

* Jorgen Grahn, on 08.06.2010 22:09:
Which is a slightly convoluted way of saying "only the things that are
surely irrelevant to the interface are disregarded in the interface".

That turns out to be a circular argument, because (see below) the 'const' is
only irrelevant because of the current rules that it is disregarded...

When you use call-by-value, you couldn't care less if the callee
modifies his copy of the value. Easy to remember, and makes good
sense.

Yes, easy to remember, but a bit misleading.

Consider the translation of a function

void foo( T v );

to machine code, and in particular, for sizeof(T) > something, implementing the
function in machine code as if it were declared as

void foo( T* v );

which in certain cases may reduce the argument passing from a large number of
bytes to four or eight bytes.

If there is possible aliasing, that some code invoked by foo (or perhaps
executing in a separate thread) accesses the caller's actual argument, then this
optimization is unsafe unless a copy of the actual argument is made. The copy
can made at the call site or internally in foo. The latter was not uncommon in
the old days, at least for Pascal compilers, because when foo does nothing with
the argument but passing it on, then the copying can be avoided.

Placing the copying at the call site, on the other hand, allows that copying to
be elided when the compiler can prove that there's no aliasing and can assume
that foo does not change the actual argument via the passed pointer. So this
could be an optimization. But if the source code implementation of foo is e.g.

void foo( T v ) { v = bar(); blahblah(); use( v ); }

then, at least for local code generation, the code generated for foo would also
have to copy v, resulting in potentially two copy operations per call (one at
each call site and one inside foo) instead of just one: hardly an optimization!

However, if a 'T const' in the declaration guaranteed a 'T const' in the
definition, then the compiler could do this optimization, perhaps with some
wording in the standard that you're in UB-land if you cast away the const.
Because then the compiler could assume that foo would not modify the actual
argument via the pointer. All it'd have to do would be to prove no aliasing for
any particular call site, and then it could just pass a pointer with no copying.

Essentially, any restriction enforced by the type system increases what is known
and so may facilitate some optimization, not just correctness. ;-)

And so also here.


Cheers,

- Alf
 
A

Alf P. Steinbach

* Paavo Helde, on 09.06.2010 00:33:
[...]
Placing the copying at the call site, on the other hand, allows that
copying to be elided when the compiler can prove that there's no
aliasing and can assume that foo does not change the actual argument
via the passed pointer. So this could be an optimization. But if the
source code implementation of foo is e.g.

void foo( T v ) { v = bar(); blahblah(); use( v ); }

then, at least for local code generation, the code generated for foo
would also have to copy v, resulting in potentially two copy
operations per call (one at each call site and one inside foo) instead
of just one: hardly an optimization!

However, if a 'T const' in the declaration guaranteed a 'T const' in
the definition, then the compiler could do this optimization, perhaps
with some wording in the standard that you're in UB-land if you cast
away the const. Because then the compiler could assume that foo would
not modify the actual argument via the pointer. All it'd have to do
would be to prove no aliasing for any particular call site, and then
it could just pass a pointer with no copying.

If the compiler can prove the function does not modify the argument, it
can apply this optimization anyway, regardless of whether there is
'const' present or not.

That "if" is pretty big: with the rules that we have it would require whole
program optimization and is not possible with dynamic libraries without
introducing overhead (the opposite of optimization).

Since the context has been lost, and you're arguing below is if the context was
very different than what it actually was:

The context for the above quote was whether a possible rationale for the current
rules held water or not, and it did not hold water.

And about adding new UB in the language - I would have thought there is
already too much of that. Besides, such a change could break existing
code in very annoying ways.

I discussed the rationale for the existing rules. I did not discuss changing the
rules. In particular, I did not advocate introducing new UB.

Except the 'const' keyword in interfaces, which can be cast away legally
in most cases.

It's unclear what you're referring to.

Banning const_cast would indeed resolve this problem,

It's unclear what you're referring to.

but
would create many more instead.

I did not propose or discuss "banning const_cast". With C++98 rules const_cast
for an original const object is UB if you modify the result. The alternative
rules I discussed would presumably have extended that to formal arguments.

I think I will stick to passing const references, it's just one more
character to type after all.

That statement does not make sense to me, I'm sorry.


Cheers,

- Alf
 
F

Fred Zwarts

Alf P. Steinbach said:
* Fred Zwarts, on 09.06.2010 09:24:

It is standard-conforming.



Which compiler is that?

The OpenVMS C++ 6.5 compiler for Alpha processors.
I known it is an old version, but it is something I have to live with.

The code:

int func (int);

int func (const int i) {
return i;
}

results in:

%CXX-W-NOTQUACOMPREDEC, declaration is not qualifier compatible with
"int func(int)" (declared at line 1)
at line number 3 in file SCRATCH_ROOT:<KLAD>T.CPP;1

This warning is issued even when the compiler runs in ANSI-standard mode.
It should be noted that this warning can be suppressed with a compiler option.
 
Ö

Öö Tiib

The OpenVMS C++ 6.5 compiler for Alpha processors.
I known it is an old version, but it is something I have to live with.

The code:

int func (int);

int func (const int i) {
return i;

}

results in:

%CXX-W-NOTQUACOMPREDEC, declaration is not qualifier compatible with
"int func(int)" (declared at line 1)
at line number 3 in file SCRATCH_ROOT:<KLAD>T.CPP;1

This warning is issued even when the compiler runs in ANSI-standard mode.
It should be noted that this warning can be suppressed with a compiler option.

It should be also noted that compiler may issue diagnostics about
everything that it finds rude, funny, outstanding or otherwise
remarkable during compiling. If it does not entirely refuse to compile
a piece of code that is valid by standard it is all OK. Alf has whole
entry in his blog how to turn off sillywarnings (his term) of
microsoft compilers.
 
J

Jorgen Grahn

* Jorgen Grahn, on 08.06.2010 22:09:

That turns out to be a circular argument, because (see below) the 'const' is
only irrelevant because of the current rules that it is disregarded...

I was a bit too lazy to read your longer text carefully, but I suppose
I'm relying on the traditional meaning of call-by-value here, the one
you're taught in CS classes. *That* is what makes the const irrelevant
to the caller. I don't see C++ moving away from call-by-value any time
soon.

/Jorgen
 
J

Jorgen Grahn

....
And about adding new UB in the language - I would have thought there is
already too much of that.

Surely it's already UB to cast away const on something which isn't
already const?

/Jorgen
 
A

Alf P. Steinbach

* Jorgen Grahn, on 12.06.2010 09:01:
Surely it's already UB to cast away const on something which isn't
already const?

You probably meant to write the opposite, and even then it's not quite there
(the UB is for casting away const on something that was originally const, and
modifying).

But anyway what you communicate is that I have advocated a language change.

I have not.


Cheers,

- Alf
 
A

Alf P. Steinbach

* Jorgen Grahn, on 12.06.2010 08:58:
I was a bit too lazy to read your longer text carefully, but I suppose
I'm relying on the traditional meaning of call-by-value here, the one
you're taught in CS classes. *That* is what makes the const irrelevant
to the caller.

Yes, the 'const' is irrelevant to the caller.

However, in your first posting you wrote "only the things that are surely
irrelevant to the interface are disregarded in the interface" as a way to
remember the rule to disregard a top level 'const': "Easy to remember, and makes
good sense".

But as I then showed one can not deduce the rule from that: it is only with the
current rules that the 'const' is irrelevant to the interface, not with other
rules that could have been chosen. And so it's a circular argument that the rule
to disregard the 'const' is a logical consequence of the 'const' being
irrelevant, which it is because of the rule to disregard to it. And so on.

I don't see C++ moving away from call-by-value any time soon.

That's probably correct. ;-)

However, the implication that anyone has argued for such a change, is incorrect.


Cheers & hth.,

- Alf
 
J

Jorgen Grahn

* Jorgen Grahn, on 12.06.2010 09:01:

You probably meant to write the opposite,

I meant to say:
"it's UB to cast away const if the thing was const to begin with"
and even then it's not quite there
(the UB is for casting away const on something that was originally const, and
modifying).

Ah, of course. That matches my usage: I sometimes may cast away const
in order to call a function which takes a Foo* when a const Foo* would
have been enough. Yes, I surely expect that to work.
But anyway what you communicate is that I have advocated a language change.

I have not.

I'm not talking about you or what you're advocating. I just thought it
strange when Paavo thought this was a new UB. The "and modifying" part
was what I was missing.

/Jorgen
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top