Anything wrong with this function?

S

Sylvester Hesp

Grizlyk said:
Exactly. There are tons of bugs, sucsessully detected by const. Most
system even have hardware const support, because try to do const violation
is just error. Const makes parts of programs more isolated so it is easyer
to maintain. Anyway no one man wants to write into M_PI due to save
memory.

I think no one here is arguing const is useless - it's all about sementics.
Declaring every local variable you never change as const, however, can be a
bridge too far. Just declaring a local var as const because it just so
happens you never change it nearly has no semantic value whatsoever. Sure
M_PI is better as const, it's a constant - you want to keep anyone from
changing it. But local short-lived temporaries which mere use is to cache a
specific calculation or complex function call? That's just a matter of
personal taste, really. For me, const in such situations don't have any
semantic meaning, I'm the only user of the variable and I'm well aware of
what I'm doing with it. It just seems arbitrary to me ("Hey, whatta ya know,
after rearranging and rewriting these statements it turns out I don't change
that temp var anymore. Let's make it const!").

As said, this is my personal opinion, and of course you are entitled to have
your own :). I'm not _discouraging_ the use of const, not even in the
situation I described above. What started this whole discussion was the
remark that you _should_ use const whenever you're not going to change a
variable. Const, from a pure C++ perspective, is not about not _going_ to
change, but about not _wanting_ to be able to change. That's a subtle
difference, and that including this being a C++ newsgroup caused my original
remark that it doesn't matter whether a function declaration declares it's
parameters as const values.
For C++ const has a special meaning, const variables can be conpile time
constants if its adress never taken.

Actually, only integral types can be compile time constants, and they are
compile-time constants no matter whether their address has been taken.

- Sylvester
 
G

Grizlyk

Sylvester said:
Declaring every local variable you never change as const, however, can be
a bridge too far. Just declaring a local var as const because it just so
happens you never change it nearly has no semantic value whatsoever.

Not sure what do you mean but if variable must not be changed by its logical
goal then the variable must be declared as const, in spite of "local"
status, i think must.

Actually, only integral types can be compile time constants, and they are
compile-time constants no matter whether their address has been taken.

No. Even integral compile-time constants will be allocated if its address
has been taken, for example

{
const int size=5;
ptintf("%s=%u is allocated to: %p\n", "size", size, &size);
}

- NOT C++ -

By the way, for some free-standing classes compiler could have const
compile-time variables of non-integral types, executing there ctor/dtor and
methods at compile time.

struct T
{
int i;
T(const int p=0):i(p){}
~T():i(p){}
};

{
const T size(5);
ptintf("%s=%u is allocated to: %p\n", "size", size.i, &size.i);
}

- NOT C++ -
 
S

Sylvester Hesp

Grizlyk said:
Not sure what do you mean but if variable must not be changed by its
logical goal then the variable must be declared as const, in spite of
"local" status, i think must.

This is what I was saying. If something _must_ not be changed, you should
declare it const. You should not declare something const just because it
appears you are not changing it (again, in _my_ opinion)
No. Even integral compile-time constants will be allocated if its address
has been taken

I know, but that was not what we were talking about. You were saying it is a
compile-time constant unless it has it's address taken. But it does not stop
from being a compile-time constant as soon as you take it's address:

const int size = 4;
int array1[size]; // valid

const int * sizePtr = &size;
int array2[size]; // still valid, even though size has it's address
taken

By the way, for some free-standing classes compiler could have const
compile-time variables of non-integral types, executing there ctor/dtor
and methods at compile time.

I think your definition of "compile-time constant" is a bit off. A
compile-time constant is a constant that can be used in places where a
compile-time constant is expected, such as the size of an array. It has
nothing to do with whether that constant has to be allocated or whether the
expression is executed at compile-time to calculate the value - these are
merely optimizations done by the compiler.

inline int foo() { return 5; }
const int constInt = foo();

Most compilers will not generate a call to foo() to initialize constInt -
they will even just place the value '5' in the binary of the executable. Yet
constInt is not a compile-time constant (e.g., you cannot use it to define
the size of an array).

- Sylvester
 
G

Grizlyk

Sylvester said:
This is what I was saying. If something _must_ not be changed, you should
declare it const. You should not declare something const just because it
appears you are not changing it (again, in _my_ opinion)

I think that we should not declare something with unknown or indefinite
goals, if variable:

- is a kind of input parameter, then it is mostly const;
- is temp storage, then it is mostly const;

- is counter-like, then it is always non const;
- is initializing by several steps, then it is always non const (and it is
sometimes problem if I need to declare later const reference to it as
alias);
- is data transfer area between units, then it is always non const;

For classes with complex behaviour all depends from its methods, we are
going to call.

I think, no other "classes" of variables exist. At least if I am not sure
what the variable I have declared will do, I use "const", to reduce possible
variations, so it looks like "const by default".

I think your definition of "compile-time constant" is a bit off. A
compile-time constant is a constant that can be used in places where a
compile-time constant is expected, such as the size of an array. It has
nothing to do with whether that constant has to be allocated or whether
the expression is executed at compile-time to calculate the value - these
are merely optimizations done by the compiler.


inline int foo() { return 5; }
const int constInt = foo();

Most compilers will not generate a call to foo() to initialize constInt -
they will even just place the value '5' in the binary of the executable.
Yet constInt is not a compile-time constant (e.g., you cannot use it to
define the size of an array).

You are right, probably I have used a wrong name "compile-time constant",
because i have said about memory usage - const variable often can be not
placed into memory (as enum).

The last is advantage in comparison with non-const, I agree, that current
compilers do not do great differences between const and non-const data
during optimisation, but const data have more chances to be optimised due to
nature of const.

- NOT C++ -

I think, that the fact (that there are no great differences between const
and non-const data during optimisation), can be changed in future, because
your example
inline int foo() { return 5; }
const int constInt = foo();

de facto is true "compile-time constant", and excactly as

#define constInt (5)

and the fact, that C++ compiler can not see it is just bad optimisation
rules.

I think, all execution, that can be done at compile time must be done at
compile time, else people will use preprocessor to avoid dummy runtime
execution.

The const modifier can help to compiler to start execution at compile time
and to convert result of execution into something like

#define constInt (5)

And as i have said, it is able even for so complex objects, as class.

- NOT C++ -
 
G

Gavin Deane

Not sure what do you mean but if variable must not be changed by its logical
goal then the variable must be declared as const, in spite of "local"
status, i think must.

I think the question is *how much* value const adds in a strictly
local scope when it is applied to an object that happens to remain
unchanged. Elsethread, Kai-Uwe Bux presented two alternatives for the
same function:

template < typename T >
void swap ( T & lhs, T & rhs ) {
T dummy ( lhs );
lhs = rhs;
rhs = dummy;
}

or

template < typename T >
void swap ( T & lhs, T & rhs ) {
const T dummy ( lhs );
lhs = rhs;
rhs = dummy;
}

Do you believe that the first is significantly poorer style than the
second?

Gavin Deane
 
K

Kai-Uwe Bux

Gavin said:
I think the question is *how much* value const adds in a strictly
local scope when it is applied to an object that happens to remain
unchanged. Elsethread, Kai-Uwe Bux presented two alternatives for the
same function:

template < typename T >
void swap ( T & lhs, T & rhs ) {
T dummy ( lhs );
lhs = rhs;
rhs = dummy;
}

or

template < typename T >
void swap ( T & lhs, T & rhs ) {
const T dummy ( lhs );
lhs = rhs;
rhs = dummy;
}

Do you believe that the first is significantly poorer style than the
second?

One remark about the example: the two versions are _not_ equivalent. For
instance the first version will correctly swap auto_ptr<int> objects
whereas you will get a compile time error with the second. It is debatable
whether that is a feature or a bug. In any case it is a difference, and the
lesson to be learned is that with templates you decisions about those
little things sometimes translate into conceptual requirements for the
types you can use to instantiate your templates.


Best

Kai-Uwe Bux
 
G

Grizlyk

Gavin said:
I think the question is *how much* value const adds in a strictly
local scope when it is applied to an object that happens to remain
unchanged. Elsethread, Kai-Uwe Bux presented two alternatives for the
same function:

template < typename T >
void swap ( T & lhs, T & rhs ) {
T dummy ( lhs );
lhs = rhs;
rhs = dummy;
}

template < typename T >
void swap ( T & lhs, T & rhs ) {
const T dummy ( lhs );
lhs = rhs;
rhs = dummy;
}

Do you believe that the first is significantly poorer style than the
second?

I think the second is better.

The first example is a kind of "freedom to make crimes", not a "freedom to
protect your base human rights" :) A man, who will maybe mainain your code
will assume, that variable "dummy" of unknown type "T" is free for writing
to it in spite of the fact, that code looks like "dummy" is readonly.

The last example "const dummy" is more explained and give a chance to the
man to reduce his work. If you say "const" you say that you have decided,
that using of the variable is limited, that better then unlimitted usage.

That is why, we even can want to have "writeonly"-like and free-used
"mutable" modifiers, just to dettect errors of usage at compile time and
ensure that we know what we are doing (do not forge to say const).
 
S

Sylvester Hesp

Grizlyk said:
I think the second is better.

As Kai-Uwe already mentioned, a const T prevents you to swap any object that
has a copy-ctor which doesn't take a const reference, such as std::auto_ptr.
Thus while the code may look better, it is not actually better as you limit
the use of the swap function - a limitation which does not make sense at all
(you should be able to swap any copy-constructable non-const variables)

- Sylvester
 
G

Grizlyk

Sylvester said:
As Kai-Uwe already mentioned, a const T prevents you to swap any object
that has a copy-ctor which doesn't take a const reference, such as
std::auto_ptr.

Do not use std::auto_ptr. Write you onw with same behaviour.
(you should be able to swap any copy-constructable non-const variables)

What the reason to make copy-constructable as non-const - in order to allow
changes of internal rvalue state during copying?

I think, objects that moves own state during copying is not copyable, but
moveable. If you want to move, it does not means you want non-const, becasue
non-const allows more than only move. In other words moveable can be const.

-- NOT C++ start --

Consider, it can be better

//move
template < typename T >
void operator < (const T &) dtor;

template < typename T >
void swap ( T & lhs, T & rhs )
{
const T dummy ( <lhs );
lhs = <rhs;
rhs = <dummy;
}

template < typename T >
void operator <->(T& lhs, T& rhs){swap<T>( lhs, rhs );}

The operator<-> can be builtin for POD types.

-- NOT C++ end --
 
G

Grizlyk

Grizlyk said:
Write you onw

means "Write your own"
-- NOT C++ start --

Consider, it can be better

//move
template < typename T >
void operator < (const T &) dtor;

as global friend operator
T::move_return_type operator < (const T &);

or as class T member
T::move_return_type T::eek:perator < ()const dtor;

But it is clear in general.
 
S

Sylvester Hesp

Grizlyk said:
Do not use std::auto_ptr. Write you onw with same behaviour.

Nonsense. Why do I need to do that? Aside from that, it was an example, the
argument holds for any type with a non-const copy ctor (don't deny their
existence!). If someone chose to implement swap with a const dummy, that's
the thing I wouldn't use and rewrite. I will not rewrite every nonconst copy
constructable type I use just because it so happens someone implemented
swap() in the way you described because he/she thought that was better
design, completely ignoring the fact that others might want to swap nonconst
copy constructable types as well, which makes perfect sense.
What the reason to make copy-constructable as non-const - in order to
allow changes of internal rvalue state during copying?

See std::auto_ptr, which you still want to be able to swap.
I think, objects that moves own state during copying is not copyable, but
moveable. If you want to move, it does not means you want non-const,
becasue non-const allows more than only move. In other words moveable can
be const.

I said copy constructable, not copyable. Subtle but important difference.
-- NOT C++ start --

Consider, it can be better

Yes, but right now we have to deal with how things are.
template < typename T >
void swap ( T & lhs, T & rhs )
{
const T dummy ( <lhs );
lhs = <rhs;
rhs = <dummy;
}

C++09 will support move semantics using reference-to-r-value (&&), but that
doesn't change the implementation of our swap function as 'dummy' is not an
r-value. It's a const l-value, it doesn't make sense to move data away from
a const l-value thus changing it's state, not even with your suggested move
operator. It's const for a reason. So your suggestion is sementically
flawed.

- Sylvester
 
G

Grizlyk

Sylvester said:
Nonsense. Why do I need to do that?

If you write "nonsense" that _you_ must write "why", not I "why not". But I
can repeat, it is not hard to me because i know a sence. Because
implementation of std::auto_ptr is based on wrong invariants. There are many
invariants in the world, but not all of them are useful and std::auto_ptr is
good example of useless invariants.

The auto_ptr idiom is simplest thing in the world:
- auto_ptr is owner of unique resource (memory area)
- if you want to pass ownership, then pass auto_ptr by value
- if you want to pass address, then pass POD pointer
- you can not use any kind of auto_ptr (non-const or const) after you pass
ownership (in fact, after "move" auto_ptr must be immediately deleted)

Remove constness in order to make only move is wrong idea and does not
require by move semantic. Move semantic required "once" support, not
"non-const", it is really different things. See below.

In the case, non-constness looks as if you will refuse from static type
check and will use only void* just because any can write wrong int value to
char memory over casted pointer.

For correct auto_ptr usage compiler can easy control "once" of move
operation at compile time.
Aside from that, it was an example, the argument holds for any type with a
non-const copy ctor (don't deny their existence!).

The "non-const copy ctor" is not "copy ctor", so if you allow your class to
be copyable - make "copy ctor"("const copy ctor"). If you allow your class
to be moveable - make "move" as "once". The "non-const copy ctor" can exist
for other things. For move can be ctor with "const &&".

If someone chose to implement swap with a const dummy, that's the thing I
wouldn't use and rewrite. I will not rewrite every nonconst copy
constructable type I use just because it so happens someone implemented
swap() in the way you described because he/she thought that was better
design, completely ignoring the fact that others might want to swap
nonconst copy constructable types as well, which makes perfect sense.

The "constructable type" type is dimly defined. What is it? If object have
been created, can its type be "non-constructable"? In opposite, moveable
well-defined and means "one created - other destroyed, only one copy at time
exist".

See std::auto_ptr, which you still want to be able to swap.

see above

I said copy constructable, not copyable. Subtle but important difference.

see above



-- NOT C++ start --
Yes, but right now we have to deal with how things are.

My code just now use it, with limitations of current C++, the line

looks like


const T dummy ( lhs );

and if T has

T::T(const T& obj):ptr(obj.move()){}

C++09 will support move semantics using reference-to-r-value (&&), but
that doesn't change the implementation of our swap function as 'dummy' is
not an r-value. It's a const l-value, it doesn't make sense to move data
away from a const l-value thus changing it's state, not even with your
suggested move operator. It's const for a reason. So your suggestion is
sementically flawed.

We must not be compatible with random "reference-to-r-value" definition, let
"reference-to-r-value" definition will be compatible with real move
semantic.

It is evidently to anybode that move has "once" argument, "once" can be
const or non-const. Consider the differences

const T dummy ( <lhs );
<lhs; //compiler must generate error here
//"double move" - "once" violation
dummy= <rhs; //compile time error

T dummy ( <lhs );
<lhs; //compiler must generate error here
//"double move" - "once" violation
dummy= <rhs; //ok

And the fact that for swap function does not strictly require const dummy,
does not say that all other functions does not require const movable.

In opposite to move, swap semantics really require non-const to its
argumets.

In addition, I think C++ must support operator <-> (swap) at least because
CPUs have the swap hardware opcodes for POD, as for *p++ and < (move)
because it is useful operation for class-wrappers.

-- NOT C++ end --
 
G

Grizlyk

Grizlyk wrote:

-- NOT C++ --
The "non-const copy ctor" is not "copy ctor", so if you allow your class
to be copyable - make "copy ctor"("const copy ctor"). If you allow your
class to be moveable - make "move" as "once". The "non-const copy ctor"
can exist for other things. For move can be ctor with "const &&".

I have some questions.

I see now, that we (or I) must have clear differences between

a) copyable parameter passed by reference
b) moveable parameter passed by reference
c) copyable parameter passed by value
d) moveable parameter passed by value

and we (or I) must declare

1. what syntax must be to declare each type of parameter
2. what kind of ctor must be used

// ***
a) copyable parameter passed by reference

The declaration sign "&" say that "data" will be created as reference of src

1. foo(const T& data)
foo(T& data)

2. T::eek:perator??? what kind of operator makes references?

{const T src; foo(src);}
{ T src; foo(src);}

// ***
b) moveable parameter passed by reference

For non-inline functions it is bad idea, because compiler will lose control
for "once" over local scope bound.

The declaration sign "&" say that "data" will be created as reference of src

1. foo(const T& data)
foo(T& data)

2. T::eek:perator??? what kind of operator makes references?

{const T src; foo(src);} //foo::data==src
{ T src; foo(src);} //foo::data==src

// ***
c) copyable parameter passed by value

1. foo(const T data)
foo(T data)

2. T::T(const T&)
/*T::T(T&)*/

// foo::data==T::T(const T& /*src*/)==src.copy()
{const T src; foo(src);}

// foo::data==T::T(const T& /*src*/)==src.copy()
{ T src; foo(src);}

Do we allow T::T(T&) to be used for "T src; foo(src);"?
Is any class T have declared only T::T(T&) copyable?

// ***
d) moveable parameter passed by value

Let declaration sign "@" say that "data" will be created by "move" from
"src"

1. foo(const T@ data)
foo(T@ data)

2. T::T(const T@ data)
/*T::T(T@)*/

// foo::data==T::T(const T@ /*src*/)== src.move()
{const T src; foo(src);}

// foo::data==T::T(const T@ /*src*/)== src.move()
{ T src; foo(src);}

Do we allow T::T(T@) to be used for "T src; foo(src);"?
Is any class T have declared only T::T(T@) moveable?

-- NOT C++ --
 
S

Sylvester Hesp

Grizlyk said:
If you write "nonsense" that _you_ must write "why", not I "why not". But
I can repeat, it is not hard to me because i know a sence.

I'm sorry but I don't understand anything of what you're trying to say here.
(in fact, after "move" auto_ptr must be immediately deleted)
Of course not. Yes, it holds a null pointer after it's content has been
moved to another auto_ptr or after a call to release(), but that doesn't
mean the autoptr itself has to be destructed. You can happily reassign it
with a new pointer
Remove constness in order to make only move is wrong idea and does not
require by move semantic. Move semantic required "once" support, not
"non-const", it is really different things. See below.

Actually it does require non-const for l-values. Otherwise you can easily
change all const variables by simply moving them to other variables.
In the case, non-constness looks as if you will refuse from static type
check and will use only void* just because any can write wrong int value
to char memory over casted pointer.

First of all, I thought the discussion whether to use const or not for local
variables was over. Secondly, this is just a bogus comparison. Of course not
using const for local vars where it does not have any semantic meaning is
way different than not using typechecking altogether.
For correct auto_ptr usage compiler can easy control "once" of move
operation at compile time.

No it can't. How can it assure it is moved only once if you're using the
variable in more than one translation unit? The compiler can't track whether
a variable already has moved. Aside from that, it's silly. As the variable
is still in a constructed state, it's state has to be well-defined (for
example, a null pointer for a std::auto_ptr). If it has a well-defined
state, that means it can be moved again. Of course, this value doesn't have
to be the same as the value you moved earlier, but still, it can be moved.
Otherwise, you'll have to restrict all access after it's contents have been
moved (which again is impossible for the compiler to enforce).
The "non-const copy ctor" is not "copy ctor", so if you allow your class
to be copyable - make "copy ctor"("const copy ctor"). If you allow your
class to be moveable - make "move" as "once". The "non-const copy ctor"
can exist for other things. For move can be ctor with "const &&".

According to the standard, a non-const copy ctor is just as well a copy ctor
as a const copy ctor (or a volatile copy ctor or a const volatile copy
ctor).
The "constructable type" type is dimly defined. What is it? If object have
been created, can its type be "non-constructable"? In opposite, moveable
well-defined and means "one created - other destroyed, only one copy at
time exist".

I didn't say "constructable type", I said "copy constructable type", which
is of course a type with an accessible and sensible copy constructor.
My code just now use it, with limitations of current C++, the line


looks like


const T dummy ( lhs );

and if T has

T::T(const T& obj):ptr(obj.move()){}

as Ntest::auto_ptr<> do, all works as moving, not copying.

See above.
We must not be compatible with random "reference-to-r-value" definition,
let "reference-to-r-value" definition will be compatible with real move
semantic.

It is. Read these papers:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html

It is evidently to anybode that move has "once" argument, "once" can be
const or non-const. Consider the differences

const T dummy ( <lhs );
<lhs; //compiler must generate error here
//"double move" - "once" violation
dummy= <rhs; //compile time error

T dummy ( <lhs );
<lhs; //compiler must generate error here
//"double move" - "once" violation
dummy= <rhs; //ok

Now you are moving TO a (const) variable. We were discussing the fact that
you were moving FROM a (const) variable. Your definition:

template < typename T >
void swap ( T & lhs, T & rhs )
{
const T dummy ( <lhs );
lhs = <rhs;
rhs = <dummy;
}

dummy is const, so the statement 'rhs = <dummy' must be ill-formed. Because
with the move, you are changing the state of dummy, which you shouldn't
because it's const.

- Sylvester
 
S

Sylvester Hesp

Grizlyk said:
Nonsense.

Great point! Why didn't I see that earlier. It's all clear to me know how
you were completely right and I was totally wrong.
/end sarcasm ;)

Look, it's clear that English isn't your native language. Which is perfectly
fine, it's not mine either - as long as we can understand eachother that's
all that matters. But in this particular case I am having problems
interpreting what you are saying.

- Sylvester
 
V

Victor Bazarov

Sylvester said:
Great point! Why didn't I see that earlier. It's all clear to me know
how you were completely right and I was totally wrong.
/end sarcasm ;)

Look, it's clear that English isn't your native language. Which is
perfectly fine, it's not mine either - as long as we can understand
eachother that's all that matters. But in this particular case I am
having problems interpreting what you are saying.

Maksim says that if you (Sylvester) use the word "nonsense", it is
your responsibility to explain why it is so, not his responsibility
to have a counter-argument. But he can repeat [what he already said]
because for him it is exactly what defines the sense of {whatever you
called 'nonsense'}. Any clearer?

V
 
S

Sylvester Hesp

Victor Bazarov said:
Sylvester said:
Look, it's clear that English isn't your native language. Which is
perfectly fine, it's not mine either - as long as we can understand
eachother that's all that matters. But in this particular case I am
having problems interpreting what you are saying.

Maksim says that if you (Sylvester) use the word "nonsense", it is
your responsibility to explain why it is so, not his responsibility
to have a counter-argument. But he can repeat [what he already said]
because for him it is exactly what defines the sense of {whatever you
called 'nonsense'}. Any clearer?

Yes, thank you.

Fair enough, my choice for the word "nonsense" might have been a bit too
aggressive, but you [Maksim] didn't supply the reasoning of rewriting
std::auto_ptr in the original posting (although it was in the follow-up).
Granted, std::auto_ptr has it's quirks, but rewriting it is not always an
option (when working with code of others), and you still want to be able to
swap it, no matter how badly it is designed (and I'm not saying it's _that_
badly designed)

- Sylvester
 
G

Grizlyk

Sylvester said:
Yes, thank you.

Fair enough, my choice for the word "nonsense" might have been a bit too
aggressive, but

You must not build your answers without proving your opinion, to force me to
refute your statements without any hints. I am forced to guess what the
sources of your decision were.

The following example

1.
A: statement, statement.
B: nonsense#1.
A: nonsense#1->new statement, nonsense#1->new statement.
B: nonsense#2.
A: nonsense#2->new statement, nonsense#2->new statement.
B: nonsense#3.

is a kinds of useless dialogue between "A" and "for(;;){wait();
cout<<"nonsense";}".
 

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
474,431
Messages
2,571,679
Members
48,796
Latest member
Greg L.

Latest Threads

Top