array of reference?

A

Alf P. Steinbach

* terminator:
I do not get it:

void f(int& iref);

can you explain how 'iref ' is passed to 'f' if it is not inlined?
The simplest way is to implement it as a pointer ,so stack register
will be decreased sizeof(int*) for passing 'iref'.

First, note that there may not be a stack register.

The C++ abstract machine implicitly defines a logical stack, a LIFO
queue, for automatic variables and function arguments, but that does not
mean that it depends on a stack register at the hardware level or
hardware support for a call stack.

That said, using a hardware stack is the most common and today in
practice the only implementation of the abstract machine's stack.

First consider the case where the function doesn't use iref. In that
case the argument can be optimized away.

Second, consider the case where f doesn't assign to iref or take its
address. In that case, the compiler is allowed to just pass the int
value. And sizeof(int) can be much less than sizeof(int*).

Third, consider the case where in all calls of f, iref is a local
variable of the calling function, at the same offset in the stack frame.
In that case, again, the argument passing can be optimized away. Not
that I know of any C++ implementation that does that.

Fourth, consider the case of a segmented architecture where all calls of
f are passed a reference to a variable in the currently established
default data segment of the caller. In that case, an offset suffices,
no need for a full segment selector + offset pointer. That optimization
would screw up any processing of "..." arguments depending on the
assumption that the reference is the size of a pointer, if C++ allowed
the second argument to va_start to be a reference (it's currently UB).

Common to all these cases is that because references do not have size at
the abstract machine level, and are not objects at the abstract machine
level, the compiler is free to optimize or do anything.

All the compiler needs to do -- barring use of compiler-specific
language extensions -- is to make sure the requirements of the
abstract machine are fulfilled, treating the reference as an alias.

Cheers, & hth.,

- Alf
 
J

James Kanze

I do not get it:
void f(int& iref);
can you explain how 'iref ' is passed to 'f' if it is not
inlined? The simplest way is to implement it as a pointer ,so
stack register will be decreased sizeof(int*) for passing
'iref'.

First, inlining or not is irrelevant. The standard specifically
states that inlining has no effect on a function, so it can't
possibly the size of an argument to the function can't possibly
depend on that. Second, how the compiler passes arguments with
reference type is it's business, not mine. My compiler
certainly doesn't pass them on the stack; it puts them in a
register. A 64 bit register, even when I'm compiling in 32 bit
mode (where pointers are 32 bits). And on my compiler, the
actual memory space used by a reference in a structure will
depend on what is in front of and behind the reference. (The
same is true of pointers, of course.)

What is relevant is that the compiler says that sizeof( char& )
== 1, and sizeof( T& ) == sizeof( T ), regardless of the size of
a pointer. A reference doesn't have size. At least not of its
own. A reference is an other name for a variable or a value,
and its "size" is the size of whatever it names.
 
J

James Kanze

* terminator:
First, note that there may not be a stack register.
The C++ abstract machine implicitly defines a logical stack, a
LIFO queue, for automatic variables and function arguments,
but that does not mean that it depends on a stack register at
the hardware level or hardware support for a call stack.
That said, using a hardware stack is the most common and today in
practice the only implementation of the abstract machine's stack.

Sort of. In practice, except for Intel architecture (which is
notoriously poor in registers), I don't know of any case where
arguments are passed on the stack. They're passed in registers.
On a modern Sun Sparc (v9 or later), those registers are always
64 bits: 8 bytes. So a reference will be passed in 8 bytes. As
will a char, a short, an int, a long or a long long, all
pointers (even in 32 bit mode, where a pointer is normally only
4 bytes), and all enums. (None of which has anything to do with
the size of the type in the C++ sense, but I'm sure you know
that already.)

Also, in addition to a stack register, the Sparc has a register
stack. When you call a function, it is shifted down 192 bytes,
regardless of the number and types of the arguments. Does this
mean that if I call a function with one reference argument, the
size of the reference is 192, but if I call it with 2, the size
is 96?

[...]
Common to all these cases is that because references do not
have size at the abstract machine level, and are not objects
at the abstract machine level, the compiler is free to
optimize or do anything.

Note that unless you actually output the value of a pointer,
that's fairly true for pointers as well. All the actual machine
has to do is ensure that the observable behavior is the same as
one possible instantiation of the abstract machine.
All the compiler needs to do -- barring use of
compiler-specific language extensions -- is to make sure the
requirements of the abstract machine are fulfilled, treating
the reference as an alias.

Just to add to the confusion: according to the standard,
references do have size, since sizeof( T& ) is a perfectly
legal, well defined instruction. And a reference to a pointer
does have the size of a pointer. Just as a reference to an int
has the size of an int, and a reference to a double has the size
of a double. And on my machine, a reference to an int[1000000]
has a size of 8000000---good thing pointers aren't that big.

Also, C++ guarantees that a is the same as *(a + i), and that
if a has type T[] (or T*), (char*)a + sizeof(T)*i == &a. Now
explain what that last expression does where T is a reference.
(I think new T[n] would also be a bit of a problem if T could
have a reference type. Or even simply new T---which guarantees
that operator new( sizeof( T ) ) will be called.)
 
J

James Kanze

I said in case of usage as **an argument to a none inlined
function**.

Which is a pretty meaningless statement. With Sun CC, on a
Sparc, when I call something like f(int&), the compiler
decrements the stack pointer by 192. What does this tell us
about the size of a reference, or the size of the return value,
or anything else?
 
A

Alf P. Steinbach

* James Kanze:
Sort of. In practice, except for Intel architecture (which is
notoriously poor in registers), I don't know of any case where
arguments are passed on the stack.

Recursion.

Cheers,

- Alf
 
J

James Kanze

On Dec 6, 5:21 pm, Abhishek Padmanabh <[email protected]>
wrote:

[...]
Off-topic, but I recently bumped into std::accumulate which the C++98
and probably C++03 mention as to not have any side-effects but this
version had more explicit explaination that it would not change the
objects pointed to by the iterators, invalidate them etc. And not
knowing that we just got confused (got revealed later, though).

Not off-topic for the group. std::accumulate is one of those
wierd functions in the STL designed to do things the hard (or
slow) way. In this case, by using "accu = accu + *iter" or
"accu = binop( accu, *iter )", rather than "accu += *iter" or
"binop( accu, *iter )". For my digest hashing classes (MD5,
SHA1, etc.), I've created a special binary operator which does
the equivalent of a +=, and returns a special type for which the
accumulator has a no-op assignment operator. This avoids
copying the accu (which in the case of such digests, has
non-trivial state), and results in a speed up of about 8 times
faster.

It's also officially illegal, although I can't imagine an
implementation where it would fail. (It doesn't trigger the
concept checks in the more recent versions of g++, and it passes
all of the usual tests with VC++, g++ and Sun CC---the latter
with both the Rogue Wave library and the STL port.) But a
factor of 8! I offer it in the interface of the classes,
documenting that it depends on undefined behavior doing what one
would expect, and leave the rest to the conscience (or the
application requiremens) of the user.
 
J

James Kanze

* James Kanze:
Recursion.

Even then, the *arguments* are passed in registers. (You can't
have the API of a function call depending on whether the
function is recursive or not.) Naturally, the function will
take whatever steps are necessary to avoid overwriting the value
(if it still needs it, of course) before placing the argument of
the next call in the register. On a Sparc, of course, nothing
is necessary; each function automatically gets a (partially) new
set of registers.
 
T

terminator

No, references don't even have to have a physical representation.
Analogous to typedef, a reference only introduces a new name for an object.
It turns out that usually memory for an address is allocated for reference
parameters and reference object members, but it would be a pretty poor
compiler that allocated anything at all for a local reference.

joe

that is just what I wrote.maybe I am so bad a composer.

regards,
FM.
 
A

Abhishek Padmanabh

On Dec 6, 5:21 pm, Abhishek Padmanabh <[email protected]>
wrote:

[...]
Off-topic, but I recently bumped into std::accumulate which the C++98
and probably C++03 mention as to not have any side-effects but this
version had more explicit explaination that it would not change the
objects pointed to by the iterators, invalidate them etc. And not
knowing that we just got confused (got revealed later, though).

Not off-topic for the group. std::accumulate is one of those
wierd functions in the STL designed to do things the hard (or
slow) way. In this case, by using "accu = accu + *iter" or
"accu = binop( accu, *iter )", rather than "accu += *iter" or
"binop( accu, *iter )". For my digest hashing classes (MD5,
SHA1, etc.), I've created a special binary operator which does
the equivalent of a +=, and returns a special type for which the
accumulator has a no-op assignment operator. This avoids
copying the accu (which in the case of such digests, has
non-trivial state), and results in a speed up of about 8 times
faster.

It's also officially illegal, although I can't imagine an
implementation where it would fail. (It doesn't trigger the
concept checks in the more recent versions of g++, and it passes
all of the usual tests with VC++, g++ and Sun CC---the latter
with both the Rogue Wave library and the STL port.) But a
factor of 8! I offer it in the interface of the classes,
documenting that it depends on undefined behavior doing what one
would expect, and leave the rest to the conscience (or the
application requiremens) of the user.

1. How do you possibly capture the return of binary_op without copying
the value?
2. What makes it illegal?

Is this assignment such an overload as to give 8 times the efficiency?
 
A

Alf P. Steinbach

* James Kanze:
Even then, the *arguments* are passed in registers. (You can't
have the API of a function call depending on whether the
function is recursive or not.) Naturally, the function will
take whatever steps are necessary to avoid overwriting the value
(if it still needs it, of course) before placing the argument of
the next call in the register. On a Sparc, of course, nothing
is necessary; each function automatically gets a (partially) new
set of registers.

I fail to see how you can think that's anything but a hardware stack.

Cheers,

- Alf
 
T

terminator

Even then, the *arguments* are passed in registers. (You can't
have the API of a function call depending on whether the
function is recursive or not.) Naturally, the function will
take whatever steps are necessary to avoid overwriting the value
(if it still needs it, of course) before placing the argument of
the next call in the register. On a Sparc, of course, nothing
is necessary; each function automatically gets a (partially) new
set of registers.

Some sort of storage with some size is essential to any none
resolvable parameter to a function (including references).But I
believe that in such case the size of storage cannot become less than
a complete pointer(segment, offset or whatever essential to uniquely
point to a specific memmory location).That is because generally it is
not predictable where the candidiate objects to be passed by ref are
to be stored.
That is what I had to write .
If I am wrong about the stack I do take it back.

regards,
FM.
 
T

terminator

* James Kanze:







I fail to see how you can think that's anything but a hardware stack.

Maybe it is only my imagination ,but I can imagine a machine that just
does not have a push/pop pair and does not inc/dec a specific register
automatically and the call/ret code needs to handle it
manually ;something like this:

;//call:
move *SReg PC;//PC is the program counter
add SReg 1;
jump where;

;//ret:
sub SReg 1;
jump *SReg;

Regards,
FM.
 
T

terminator

On Dec 6, 5:21 pm, Abhishek Padmanabh <[email protected]>
wrote:
Off-topic, but I recently bumped into std::accumulate which the C++98
and probably C++03 mention as to not have any side-effects but this
version had more explicit explaination that it would not change the
objects pointed to by the iterators, invalidate them etc. And not
knowing that we just got confused (got revealed later, though).
Not off-topic for the group. std::accumulate is one of those
wierd functions in the STL designed to do things the hard (or
slow) way. In this case, by using "accu = accu + *iter" or
"accu = binop( accu, *iter )", rather than "accu += *iter" or
"binop( accu, *iter )". For my digest hashing classes (MD5,
SHA1, etc.), I've created a special binary operator which does
the equivalent of a +=, and returns a special type for which the
accumulator has a no-op assignment operator. This avoids
copying the accu (which in the case of such digests, has
non-trivial state), and results in a speed up of about 8 times
faster.
It's also officially illegal, although I can't imagine an
implementation where it would fail. (It doesn't trigger the
concept checks in the more recent versions of g++, and it passes
all of the usual tests with VC++, g++ and Sun CC---the latter
with both the Rogue Wave library and the STL port.) But a
factor of 8! I offer it in the interface of the classes,
documenting that it depends on undefined behavior doing what one
would expect, and leave the rest to the conscience (or the
application requiremens) of the user.

1. How do you possibly capture the return of binary_op without copying
the value?

'bin_op' performs an in-place calculation just as in 'a+=b'.On some
machines - for intrinsic types - 'a+=b' is compiled just to one
machine instruction and is faster than 'a=a+b' if it is not optimized.
However, with function objects the compiler has less chance to
optimize the code and in-place calculation is better for sure.
2. What makes it illegal?

Standard specfies what is defined behavior and what is Undefined
Behavior.This is a silent error(no dignostic is generated) because the
tool is not used as it was supposed to (fancy using a baseball bat as
a hammer).It is obvious that Mr. Kanze cheats on the library and
compiler ;for he has got to pay the cost of providing a dummy return
type for his 'bin_op' and an extra no op assignment/cast operator .
Is this assignment such an overload as to give 8 times the efficiency?

in place calculation is generally faster than otherwise that is: 'a
+=b' is often faster than 'a+b' since the former returns by ref while
the later returns by value,So far we have avoided an **assignment**
and at least one **construction** .Now add the overhead for extra
register load/unload time due to alternating operations(1.call
operation,2.assign the return) in case of none-in-place calculation
and you see that 8 is not that wierd(assuming it to be an experimental
factor not a theoretical one).

regards,
FM.
 
J

James Kanze

On Dec 7, 7:23 pm, "Alf P. Steinbach" <[email protected]> wrote:

[...]
It's obviously a stack, but you don't pass arguments "on the
stack"; it all happens behind the scenes.

On the 8086, I also used a Pascal compiler which passed
arguments in a static area; saving it on the stack in case a
recursive call was detected. The arguments were never passed on
the stack, but the local context (including any arguments) were
copied onto the stack if necessary.
Maybe it is only my imagination, but I can imagine a machine that just
does not have a push/pop pair and does not inc/dec a specific register
automatically and the call/ret code needs to handle it
manually ;something like this:
;//call:
move *SReg PC;//PC is the program counter
add SReg 1;
jump where;
;//ret:
sub SReg 1;
jump *SReg;

It's not you imagination---I'd guess that this is the case for
most traditional hardware architectures. Even today, IBM
mainframes don't have a push and a pop, and don't save the
return address on the stack, but rather in a register.

Of the early machines, the Burroughs machines were stack based:
they didn't have registers, just top of stack. Other than such
special cases, however, I think that the PDP-11 was the first
machine which had a hardware stack (via auto-increment and
auto-decrement instructions).
 
J

James Kanze

Some sort of storage with some size is essential to any none
resolvable parameter to a function (including references).

Some sort of storage is necessary any time there is state.
That's obvious. That doesn't mean that such state has size, in
the C++ sense---functions definitely require storage, but don't
have size either.
 
J

James Kanze

On Dec 6, 5:21 pm, Abhishek Padmanabh <[email protected]>
wrote:
[...]
Off-topic, but I recently bumped into std::accumulate
which the C++98 and probably C++03 mention as to not have
any side-effects but this version had more explicit
explaination that it would not change the objects pointed
to by the iterators, invalidate them etc. And not knowing
that we just got confused (got revealed later, though).
Not off-topic for the group. std::accumulate is one of those
wierd functions in the STL designed to do things the hard (or
slow) way. In this case, by using "accu = accu + *iter" or
"accu = binop( accu, *iter )", rather than "accu += *iter" or
"binop( accu, *iter )". For my digest hashing classes (MD5,
SHA1, etc.), I've created a special binary operator which does
the equivalent of a +=, and returns a special type for which the
accumulator has a no-op assignment operator. This avoids
copying the accu (which in the case of such digests, has
non-trivial state), and results in a speed up of about 8 times
faster.
It's also officially illegal, although I can't imagine an
implementation where it would fail. (It doesn't trigger the
concept checks in the more recent versions of g++, and it
passes all of the usual tests with VC++, g++ and Sun
CC---the latter with both the Rogue Wave library and the STL
port.) But a factor of 8! I offer it in the interface of
the classes, documenting that it depends on undefined
behavior doing what one would expect, and leave the rest to
the conscience (or the application requiremens) of the user.
1. How do you possibly capture the return of binary_op without copying
the value?

My binary operator works pretty much like +=; it modifies its
first argument.
2. What makes it illegal?

The standard.
Is this assignment such an overload as to give 8 times the
efficiency?

It's not an overload. I use it as a binary operator for
std::accumulate, rather than my operator+. Doing so does result
in the code running 8 times faster. (And the time necessary to
calculate the digest is often a critical element in the
runtime.)
 
A

Abhishek Padmanabh

On Dec 6, 5:21 pm, Abhishek Padmanabh <[email protected]>
wrote:
[...]
Off-topic, but I recently bumped into std::accumulate
which the C++98 and probably C++03 mention as to not have
any side-effects but this version had more explicit
explaination that it would not change the objects pointed
to by the iterators, invalidate them etc. And not knowing
that we just got confused (got revealed later, though).
Not off-topic for the group. std::accumulate is one of those
wierd functions in the STL designed to do things the hard (or
slow) way. In this case, by using "accu = accu + *iter" or
"accu = binop( accu, *iter )", rather than "accu += *iter" or
"binop( accu, *iter )". For my digest hashing classes (MD5,
SHA1, etc.), I've created a special binary operator which does
the equivalent of a +=, and returns a special type for which the
accumulator has a no-op assignment operator. This avoids
copying the accu (which in the case of such digests, has
non-trivial state), and results in a speed up of about 8 times
faster.
It's also officially illegal, although I can't imagine an
implementation where it would fail. (It doesn't trigger the
concept checks in the more recent versions of g++, and it
passes all of the usual tests with VC++, g++ and Sun
CC---the latter with both the Rogue Wave library and the STL
port.) But a factor of 8! I offer it in the interface of
the classes, documenting that it depends on undefined
behavior doing what one would expect, and leave the rest to
the conscience (or the application requiremens) of the user.
1. How do you possibly capture the return of binary_op without copying
the value?

My binary operator works pretty much like +=; it modifies its
first argument.
2. What makes it illegal?

The standard.

Yeah, :) but which clause? What is it that you are violating?
 
A

Andrey Tarasevich

siddhu said:
why can't we have array of references.

Allowing arrays of references would introduce a number of rather convoluted
complications into language specification. I hope you know that arrays in C++
(inherited from C) are already rather strange objects possessing a number of
specific properties that make them stand out from other objects (like being
non-assignable, non-copy constructible, requiring dedicated initialization
syntax etc.) References are also tricky (non-objects as they are). Combining the
former and the latter together would lead to very complicated specification for
arrays. Apparently, the standardization committee decided to take the easy and
lazy way out and simply declare that you can't have arrays of references.

There was a number of technical reasons stated in this thread already (like
"references being non-objects" and "references having no size"), but in reality
no technical reason, of course, can be taken as a serious explanation. There's
no real technical difference in having references as members of arrays or having
references as members of any other aggregate type (members of a class). Yet it
is allowed to have class members of reference type in C++. This immediately
overthrows any technical reasoning against arrays of references, leaving us with
political explanations only.

Arrays, inherited from C, were seen as "broken beyond repair" in C++ from the
very beginning, so nobody wanted to spend too much time on creating an elaborate
specification for them. Any time any complications rose with any potential array
features, these feature were simply removed from the language. This is the real
answer to your question.
 
A

Abhishek Padmanabh

Allowing arrays of references would introduce a number of rather convoluted
complications into language specification. I hope you know that arrays in C++
(inherited from C) are already rather strange objects possessing a number of
specific properties that make them stand out from other objects (like being
non-assignable, non-copy constructible, requiring dedicated initialization
syntax etc.) References are also tricky (non-objects as they are). Combining the
former and the latter together would lead to very complicated specification for
arrays. Apparently, the standardization committee decided to take the easy and
lazy way out and simply declare that you can't have arrays of references.

I don't think arrays being non-copyable, non-assignable could be a
reason for not having array of references. After all, references are
non-copyable/non-assignable. That would have been supportive as that
is more in line to the properties of arrays. Initialization is a
problem, but it is the (almost?) same as having an array of const
objects. The standard does not prohibit array of const objects. For
example, this is ok:

struct A{
A(int){}
};

int main()
{
const A array[2] = {A(1), A(2)};
}

The same could have been possible for references as well. Which brings
us to an interesting point: arrays in C++ decay to pointers. So, when
passing this array to a function, you would actually be having a array
that decayed to a pointer whose value would be the pointer to the
first element in the array: array, or &array[0]. And as per the
standards a pointer to a reference is not allowed. I think, this is
because of the very same reason that has been pointed out earlier -
that references need not have size. So, I think, the answer that
arrays are not objects/arrays need not have size is the actual reason
why there can't be array of references.
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,147
Latest member
CarenSchni
Top