Use of const for non-reference parameters

U

Urs Thuermann

I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

int foo(const int a) { ... }
int bar(const string b) { ... }

I don't see why this is useful. Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not. So why would one want to write code like that?

urs
 
I

Ian Collins

I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

int foo(const int a) { ... }
int bar(const string b) { ... }

I don't see why this is useful. Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not. So why would one want to write code like that?

It doesn't make a lot of sense. Maybe they forgot the &?
 
B

Balog Pal

Urs Thuermann said:
I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

int foo(const int a) { ... }
int bar(const string b) { ... }

I don't see why this is useful.

Usefulness is the same as for any 'const' marker. The implementer of the
function decided b shall not change, express it by saying const and use the
language to reject actual modification attempts. It also makes it easy to
know at any point of the function that the input parameter is the same as at
call.
Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not.

Yep. To the caller it is not interesting. So when you declare the above
function it should be

int bar(string b);

and const placed only at the definition. In declarations the top-level const
is ignored so those are the same.

However it is a thing not widely known by programmers. So have the tendency
to confuse humans and even tools. For those practical reasons many
guidelines advise agains const-ing the function params even if otherwise
tell to make everything const. At the same time they may advise to not
mutate the params, or limit mutation to the function preamble.

It's a judgement call what you rather sacrifice.
 
M

Michael DOUBEZ

I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

        int foo(const int a) { ... }
        int bar(const string b) { ... }

I don't see why this is useful.  Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not.  So why would one want to write code like that?

Some coding standards forbid modifying the parameters - passed by
value - of a function. The reason behind is that it makes debugging
easier in case of analysis in a debugger (typically a post morten
analysis of a core dump).

This may be an enforcement of that rule. Personnaly, I try to stick to
it but I don't bother with making parameters const; leaking a coding
practice into the interface is quite distracting (and it is more
typing).
 
S

Saeed Amrollahi

I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

        int foo(const int a) { ... }
        int bar(const string b) { ... }

I don't see why this is useful.  Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not.  So why would one want to write code like that?

urs

Hi

May be, one minor use of const argument in pass-by-value is
because of coding standard and some self-document programs.
In the following declaration:
int foo(const int a);
we explicitly say, although 'a' is passed by value and the caller
passes
a copy of it, but we actually need to a value and no one
suppose to change it.

My two cents,
-- Saeed Amrollahi
 
F

Fred Zwarts \(KVI\)

"Urs Thuermann" wrote in message
I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

int foo(const int a) { ... }
int bar(const string b) { ... }

I don't see why this is useful. Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not. So why would one want to write code like that?

urs

As said already by others, it makes sense in the function body just like any
other const declaration.
For prototype declarations it makes less sense. It is superfluous (just
like the a and the b).
Sometimes prototypes are generated automatically. Such generators may copy
the const to the prototype. So that may be a reason why you see const in
prototypes. Further, I have seen one compiler which issued a warning if the
const was missing in the prototype, but present in the full function
definition. That may be another reason why you see it in prototypes.
 
N

none

It doesn't make a lot of sense. Maybe they forgot the &?

It is not needed in the interface definition (albeit not harmful per
se) but in implementation:

int foo(const int a)
{
//... use a without modifying it
}

is comparable to:

int foo()
{
const int a = getAFromSomewhere();
// ... use a without modifying it
}

The const, albeit less important than a const& parameter that
documents the interface can still be useful.
 
U

Urs Thuermann

Balog Pal said:
Yep. To the caller it is not interesting. So when you declare the
above function it should be

int bar(string b);

and const placed only at the definition. In declarations the top-level
const is ignored so those are the same.

Oh, that's new to me. I thought I have once tried

int bar(string b);
int bar(const string b) { ... }

and have got a compiler error because of prototype mismatch. And I
have seen the const in header files where I disliked it. OK, now I
have tried again and I see I was false on that. The above works
perfectly and then I see it might actually make sense to have the
const in the defintion only, e.g. to make debugging easier as pointed
out in this thread by others.
However it is a thing not widely known by programmers. So have the
tendency to confuse humans and even tools. For those practical
reasons many guidelines advise agains const-ing the function params
even if otherwise tell to make everything const. At the same time
they may advise to not mutate the params, or limit mutation to the
function preamble.

I was unaware of this also. But neither that nor the existence of
tools that have problems with it would in my eyes count as a reason
not to use it: tools can be fixed, programmers should learn the
language. Like I have done a little bit more now.

Thanks
urs
 
M

Marc

Urs said:
Oh, that's new to me. I thought I have once tried

int bar(string b);
int bar(const string b) { ... }

and have got a compiler error because of prototype mismatch. And I
have seen the const in header files where I disliked it. OK, now I
have tried again and I see I was false on that. The above works
perfectly and then I see it might actually make sense to have the
const in the defintion only, e.g. to make debugging easier as pointed
out in this thread by others.

Note that Oracle's compiler has a bug there where it does not ignore
the const. And since this is part of the ABI, they cannot fix it.
 
H

Howard Hinnant

I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

        int foo(const int a) { ... }
        int bar(const string b) { ... }

I don't see why this is useful.  Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not.  So why would one want to write code like that?

There is a potential performance penalty with your second example
(const heavy-weight-type). Consider this slight modification of your
example bar:

string bar(const string b);

If this function does nothing but modify a copy of b and return it,
then this is seriously prematurely pessimized, especially in C++11:

string bar(const string b) // bad
{
string x(b);
x += "more text";
return x;
}

This should instead be written:

string bar(string b) // good
{
b += "more text";
return b;
}

When the argument to bar is an lvalue string, the first implementation
does 2 copies and 1 move. The second implementation will do 1 copy
and 1 move.

When the argument to bar is an rvalue string, the first implementation
does 1 copy and 1 move. The second implementation will do 0 copies
and 1 move.

Either way, the first implementation is always too expensive by 1
copy.

Howard
 
M

Michael DOUBEZ

There is a potential performance penalty with your second example
(const heavy-weight-type).  Consider this slight modification of your
example bar:

   string bar(const string b);

If this function does nothing but modify a copy of b and return it,
then this is seriously prematurely pessimized, especially in C++11:

   string bar(const string b)  // bad
   {
        string x(b);
        x += "more text";
        return x;
   }

This should instead be written:

   string bar(string b)  // good
   {
        b += "more text";
        return b;
   }

When the argument to bar is an lvalue string, the first implementation
does 2 copies and 1 move.  The second implementation will do 1 copy
and 1 move.

When the argument to bar is an rvalue string, the first implementation
does 1 copy and 1 move.  The second implementation will do 0 copies
and 1 move.

Either way, the first implementation is always too expensive by 1
copy.

Are you sure of that ? Did you observe it on a compiler ?

Because I would say:
- In the first case, the caller reserves the size of one string on
the stack then put a copy of the string in parameter. The called
function then makes a copy of the parameter in the placeholder of the
value returned and then apply the append.
- In the second state, the caller does exactly the same (he has no
way of knowing the parameter is the value returned). The caller
modifies its parameter and then copy it to the return placeholder.

In fact, with this interpretation, the second solution is less
efficient because more data are copied.

If the compiler can move, there will be in both case a move and an
append (maybe not in the same order).

In case of inlining, I expect the generated code will be exactely the
same.

Yet, this is pure speculation because compiler have ways to optimise
code beyond our abilities.
 
H

Howard Hinnant

Are you sure of that ? Did you observe it on a compiler ?

Did you?

#include <iostream>

struct string
{
string() {}
string(const string&) {std::cout << "copy construct\n";}
string& operator=(const string&) {std::cout << "copy assign\n";
return *this;}
string(string&&) {std::cout << "move construct\n";}
string& operator=(string&&) {std::cout << "move assign\n"; return
*this;}

void operator+=(const char*) {}
};

#if SLOW

string bar(const string b)
{
string x(b);
x += "more text";
return x;
}

#else

string bar(string b)
{
b += "more text";
return b;
}

#endif

int main()
{
std::cout << "test lvalue\n";
string s;
string s1 = bar(s);
std::cout << "\ntest rvalue\n";
string s2 = bar(string());
}

$ clang++ -std=c++0x -stdlib=libc++ -DSLOW test.cpp
$ a.out
test lvalue
copy construct
copy construct

test rvalue
copy construct

$ clang++ -std=c++0x -stdlib=libc++ test.cpp
$ a.out
test lvalue
copy construct
move construct

test rvalue
move construct
Yet, this is pure speculation because compiler have ways to optimise
code beyond our abilities.

<cough>

Howard
 
J

Johannes Schaub (litb)

Urs said:
I'm working on a project that uses const for function parameters that
are neither references nor pointers, i.e. something like this:

int foo(const int a) { ... }
int bar(const string b) { ... }

I don't see why this is useful. Since the argument is copied to the
called function it doesn't matter to the caller whether the argument
is modified or not. So why would one want to write code like that?

urs

See http://stackoverflow.com/questions/1554750/c-const-keyword-use-
liberally/1554762
 
G

Gerhard Fiedler

Howard said:
Are you sure of that ? Did you observe it on a compiler ? Did you? [...]
Yet, this is pure speculation because compiler have ways to optimise
code beyond our abilities.
$ clang++ -std=c++0x -stdlib=libc++ -DSLOW test.cpp

I don't know this compiler, but shouldn't you enable all possible
optimizations when talking about performance and optimization?
$ a.out
test lvalue
copy construct
copy construct

test rvalue
copy construct
[...]

This seems to be the expected behavior of unoptimized code, without any
performance data.

Gerhard
 
B

Balog Pal

Gerhard Fiedler said:
$ a.out
test lvalue
copy construct
copy construct

test rvalue
copy construct
[...]

This seems to be the expected behavior of unoptimized code, without any
performance data.

And of optimized code too, because constructors gained observable behavior
emitting the text that the compiler is not allowed to kill in this context.
As usual it is easy to forge an experiment to "prove" whatever point.
 
T

Thomas J. Gritzan

Am 17.06.2011 18:40, schrieb Balog Pal:
Gerhard Fiedler said:
$ a.out
test lvalue
copy construct
copy construct

test rvalue
copy construct
[...]

This seems to be the expected behavior of unoptimized code, without any
performance data.

And of optimized code too, because constructors gained observable
behavior emitting the text that the compiler is not allowed to kill in
this context. As usual it is easy to forge an experiment to "prove"
whatever point.

The observable behaviour of copy constructors can be omitted.
 
J

Joshua Maurice

Gerhard Fiedler said:
$ a.out
test lvalue
copy construct
copy construct
test rvalue
copy construct
[...]
This seems to be the expected behavior of unoptimized code, without any
performance data.

And of optimized code too, because constructors gained observable behavior
emitting the text that the compiler is not allowed to kill in this context.
As usual it is easy to forge an experiment to "prove" whatever point.

As "Thomas J. Gritzan" said tersely else-thread, there is an explicit
provision in the standard, apart from the "as if" rule, that allows a
compiler in some situations to omit copies and copy constructor calls
and destructor calls, even when the program's observable execution
changes as a result. See
12.8 Copying class objects / 15
 
H

Howard Hinnant

Howard said:
Are you sure of that ? Did you observe it on a compiler ? Did you? [...]
Yet, this is pure speculation because compiler have ways to optimise
code beyond our abilities.
$ clang++ -std=c++0x -stdlib=libc++ -DSLOW test.cpp

I don't know this compiler, but shouldn't you enable all possible
optimizations when talking about performance and optimization?
$ a.out
test lvalue
copy construct
copy construct
test rvalue
copy construct
[...]

This seems to be the expected behavior of unoptimized code, without any
performance data.

You are correct that I should have posted -O3 compiled results. I
happen to know my compiler well enough to know that constructor
elision is the same for all optimization levels. The results are the
same as what I posted no matter what for this compiler.

Furthermore these results reflect all legal constructor elisions being
taken advantage of. I.e. no compiler could elide more and remain
conforming.

Howard
 
B

Balog Pal

Thomas J. Gritzan said:
The observable behaviour of copy constructors can be omitted.

Yeah, for the temporary created during copy-initialization. Not for anything
else, especially named objects, like in this case.
 
J

Juha Nieminen

Urs Thuermann said:
int foo(const int a) { ... }
int bar(const string b) { ... }

I don't see why this is useful.

I am one of those few programmers who think that constness should be
the *default*, and non-constness the exception (iow. only used when you
truly need the variable to be non-const). That is, every time I declare
a variable, I make it const unless that variable needs to be changed.
While the usefulness of this is admittedly somewhat low, it still serves
as both documentation ("this variable will not be modified during the
rest of this function") and minor code correctness checking (if a
variable is intended to not to be modified, if you get an error that
you are in fact trying to modify, then you either have a design error
in your code, or you need to "re-document" your variable declaration).
It is also remotely possible that declaring non-changing variables as
const might in a few cases help the compiler to optimize better (although
I don't have any concrete evidence of this right now).

I have to admit, though, that I seldom declare non-changing function
parameters const because it makes the function declarations longer
(especially for functions taking many parameters). I have been slowly
changing this, though. (After all, brevity should never be preferred
over quality.)
 

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,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top