What kind of optimization does const offer in C/C++? - c++

I know that where possible you should use the const keyword when passing parameters around by reference or by pointer for readability reasons. Is there any optimizations that the compiler can do if I specify that an argument is constant?
There could be a few cases:
Function parameters:
Constant reference:
void foo(const SomeClass& obj)
Constant SomeClass object:
void foo(const SomeClass* pObj)
And constant pointer to SomeClass:
void foo(SomeClass* const pObj)
Variable declarations:
const int i = 1234
Function declarations:
const char* foo()
What kind of compiler optimizations each one offers (if any)?

Source
Case 1:
When you declare a const in your program,
int const x = 2;
Compiler can optimize away this const by not providing storage for this variable; instead it can be added to the symbol table. So a subsequent read just needs indirection into the symbol table rather than instructions to fetch value from memory.
Note: If you do something like:
const int x = 1;
const int* y = &x;
Then this would force compiler to allocate space for x. So, that degree of optimization is not possible for this case.
In terms of function parameters const means that parameter is not modified in the function. As far as I know, there's no substantial performance gain for using const; rather it's a means to ensure correctness.
Case 2:
"Does declaring the parameter and/or the return value as const help the compiler to generate more optimal code?"
const Y& f( const X& x )
{
// ... do something with x and find a Y object ...
return someY;
}
What could the compiler do better? Could it avoid a copy of the parameter or the return value?
No, as argument is already passed by reference.
Could it put a copy of x or someY into read-only memory?
No, as both x and someY live outside its scope and come from and/or are given to the outside world. Even if someY is dynamically allocated on the fly within f() itself, it and its ownership are given up to the caller.
What about possible optimizations of code that appears inside the body of f()? Because of the const, could the compiler somehow improve the code it generates for the body of f()?
Even when you call a const member function, the compiler can't assume that the bits of object x or object someY won't be changed. Further, there are additional problems (unless the compiler performs global optimization): The compiler also may not know for sure that no other code might have a non-const reference that aliases the same object as x and/or someY, and whether any such non-const references to the same object might get used incidentally during the execution of f(); and the compiler may not even know whether the real objects, to which x and someY are merely references, were actually declared const in the first place.
Case 3:
void f( const Z z )
{
// ...
}
Will there be any optimization in this?
Yes because the compiler knows that z truly is a const object, it could perform some useful optimizations even without global analysis. For example, if the body of f() contains a call like g( &z ), the compiler can be sure that the non-mutable parts of z do not change during the call to g().

Before giving any answer, I want to emphasize that the reason to use or not use const really ought to be for program correctness and for clarity for other developers more so than for compiler optimizations; that is, making a parameter const documents that the method will not modify that parameter, and making a member function const documents that that member will not modify the object of which it is a member (at least not in a way that logically changes the output from any other const member function). Doing this, for example, allows developers to avoid making unnecessary copies of objects (because they don't have to worry that the original will be destroyed or modified) or to avoid unnecessary thread synchronization (e.g. by knowing that all threads merely read and do not mutate the object in question).
In terms of optimizations a compiler could make, at least in theory, albeit in an optimization mode that allows it to make certain non-standard assumptions that could break standard C++ code, consider:
for (int i = 0; i < obj.length(); ++i) {
f(obj);
}
Suppose the length function is marked as const but is actually an expensive operation (let's say it actually operates in O(n) time instead of O(1) time). If the function f takes its parameter by const reference, then the compiler could potentially optimize this loop to:
int cached_length = obj.length();
for (int i = 0; i < cached_length; ++i) {
f(obj);
}
... because the fact that the function f does not modify the parameter guarantees that the length function should return the same values each time given that the object has not changed. However, if f is declared to take the parameter by a mutable reference, then length would need to be recomputed on each iteration of the loop, as f could have modified the object in a way to produce a change in the value.
As pointed out in the comments, this is assuming a number of additional caveats and would only be possible when invoking the compiler in a non-standard mode that allows it to make additional assumptions (such as that const methods are strictly a function of their inputs and that optimizations can assume that code will never use const_cast to convert a const reference parameter to a mutable reference).

Function parameters:
const is not significant for referenced memory. It's like tying a hand behind the optimizer's back.
Suppose you call another function (e.g. void bar()) in foo which has no visible definition. The optimizer will have a restriction because it has no way of knowing whether or not bar has modified the function parameter passed to foo (e.g. via access to global memory). Potential to modify memory externally and aliasing introduce significant restrictions for optimizers in this area.
Although you did not ask, const values for function parameters does allow optimizations because the optimizer is guaranteed a const object. Of course, the cost to copy that parameter may be much higher than the optimizer's benefits.
See: http://www.gotw.ca/gotw/081.htm
Variable declarations: const int i = 1234
This depends on where it is declared, when it is created, and the type. This category is largely where const optimizations exist. It is undefined to modify a const object or known constant, so the compiler is allowed to make some optimizations; it assumes you do not invoke undefined behavior and that introduces some guarantees.
const int A(10);
foo(A);
// compiler can assume A's not been modified by foo
Obviously, an optimizer can also identify variables which do not change:
for (int i(0), n(10); i < n; ++i) { // << n is not const
std::cout << i << ' ';
}
Function declarations: const char* foo()
Not significant. The referenced memory may be modified externally. If the referenced variable returned by foo is visible, then an optimizer could make an optimization, but that has nothing to do with the presence/absence of const on the function's return type.
Again, a const value or object is different:
extern const char foo[];

The exact effects of const differ for each context where it is used. If const is used while declaring an variable, it is physically const and potently resides in read-only memory.
const int x = 123;
Trying to cast the const-ness away is undefined behavour:
Even though const_cast may remove constness or volatility from any pointer or reference, using the resulting pointer or reference to write to an object that was declared const or to access an object that was declared volatile invokes undefined behavior. cppreference/const_cast
So in this case, the compiler may assume that the value of x is always 123. This opens some optimization potential (constants propagation)
For functions it's a different matter. Suppose:
void doFancyStuff(const MyObject& o);
our function doFancyStuff may do any of the following things with o.
not modify the object.
cast the constness away, then modify the object
modify an mutable data member of MyObject
Note that if you call our function with an instance of MyObject that was declared as const, you'll invoke undefined behavior with #2.
Guru question: will the following invoke undefined behavior?
const int x = 1;
auto lam = [x]() mutable {const_cast<int&>(x) = 2;};
lam();

SomeClass* const pObj creates a constant object of pointer type. There exists no safe method of changing such an object, so the compiler can, for example, cache it into a register with only one memory read, even if its address is taken.
The others don't enable any optimizations specifically, although the const qualifier on the type will affect overload resolution and possibly result in different and faster functions being selected.

Related

Are const arguments "real" constants?

AFAIK removing constness from const variables is undefined behavior:
const int i = 13;
const_cast<int&>(i) = 42; //UB
std::cout << i << std::endl; //out: 13
But are const function arguments "real" constants? Let's consider following example:
void foo(const int k){
const_cast<int&>(k) = 42; //UB?
std::cout << k << std::endl;
}
int main(){
foo(13); //out: 42
}
Seems like compiler doesn't apply the same optimizations to const int k as to const int i.
Is there UB in the second example? Does const int k help compiler to optimize code or compiler just checks const correctness and nothing more?
Example
The i in const int i = 13; can be used as constant expression (as template argument or case label) and attempts to modify it are undefined behavior. It is so for backwards compatibility with pre-C++11 code that did not have constexpr.
The declarations void foo(const int k); and void foo(int k); are declaring same function; the top level const of parameters does not participate in function's signature. Parameter k must be passed by value and so can't be "real" constant. Casting its constness away is not undefined behavior. Edit: But any attempt to modify it is still undefined because it is const object [basic.type.qualifier] (1.1):
A const object is an object of type const T or a non-mutable subobject of such an object.
By [dcl.type.cv] 4 const object can't be modified:
Except that any class member declared mutable (10.1.1) can be modified, any attempt to modify a const object during its lifetime (6.8) results in undefined behavior.
And since function parameters are with automatic storage duration a new object within its storage can't be also created by [basic.life] 10:
Creating a new object within the storage that a const complete object with static, thread, or automatic storage duration occupies, or within the storage that such a const object used to occupy before its lifetime ended, results in undefined behavior.
It is unclear why k was declared const at the first place if there is plan to cast its constness away? The only purpose of it feels to be to confuse and to obfuscate.
Generally we should favor immutability everywhere since it helps people to reason. Also it may help compilers to optimize. However where we only declare immutability, but do not honor it, there it works opposite and confuses.
Other thing that we should favor are pure functions. These do not depend on or modify any external state and have no side-effects. These also are easier to reason about and to optimize both for people and for compilers. Currently such functions can be declared constexpr. However declaring the by-value parameters as const does not help any optimizations to my knowledge, even in context of constexpr functions.
But are const function arguments "real" constants?
I think the answer is yes.
They won't be stored in ROM (for example), so casting away const will at least appear to work OK. But my reading of the standard is that the the parameter's type is const int and therefore it is a const object ([basic.type.qualifier]), and so modifying it is undefined ([dcl.type.cv]).
You can confirm the parameter's type fairly easily:
static_assert( std::is_same_v<const int, decltype(k)> );
Is there UB in the second example?
Yes, I think there is.
Does const int k help compiler to optimize code or compiler just checks const correctness and nothing more?
In theory the compiler could assume that in the following example k is not changed by the call to g(const int&), because modifying k would be UB:
extern void g(const int&);
void f(const int k)
{
g(k);
return k;
}
But in practice I don't think compilers take advantage of that, instead they assume that k might be modified (compiler explorer demo).
I'm not sure what you mean by a "real" const, but here goes.
Your const int i is a variable declaration outside of any function. Since modifying that variable would cause Undefined Behaviour, the compiler gets to assume that its value will never change. One easy optimization would be that anywhere you read from i, the compiler doesn't have to go and read the value from main memory, it can emit the assembly to use the value directly.
Your const int k (or the more interesting const int & k) is a function parameter. All it promises is that this function won't be changing the value of k. That doesn't mean that it cannot be changed somewhere else. Each invocation of the function could have a different value for k.

const and non-const versions of *static* member functions

I have two versions of the same static member function: one takes a pointer-to-const parameter and that takes a pointer-to-non-const parameter. I want to avoid code duplication.
After reading some stack overflow questions (these were all about non-static member functions though) I came up with this:
class C {
private:
static const type* func(const type* x) {
//long code
}
static type* func(type* x) {
return const_cast<type*>(func(static_cast<const type*>(x)));
}
public:
//some code that uses these functions
};
(I know juggling with pointers is generally a bad idea, but I'm implementing a data structure.)
I found some code in libstdc++ that looks like this:
NOTE: these are not member functions
static type* local_func(type* x)
{
//long code
}
type* func(type* x)
{
return local_func(x);
}
const type* func(const type* x)
{
return local_func(const_cast<type*>(x));
}
In the first approach the code is in a function that takes a pointer-to-const parameter.
In the second approach the code is in a function that takes a pointer-to-non-const parameter.
Which approach should generally be used? Are both correct?
The most important rule is that an interface function (public method, a free function other than one in a detail namespace, etc), should not cast away the constness of its input. Scott Meyer was one of the first to talk about preventing duplication using const_cast, here's a typical example (How do I remove code duplication between similar const and non-const member functions?):
struct C {
const char & get() const {
return c;
}
char & get() {
return const_cast<char &>(static_cast<const C &>(*this).get());
}
char c;
};
This refers to instance methods rather than static/free functions, but the principle is the same. You notice that the non-const version adds const to call the other method (for an instance method, the this pointer is the input). It then casts away constness at the end; this is safe because it knows the original input was not const.
Implementing this the other way around would be extremely dangerous. If you cast away constness of a function parameter you receive, you are taking a big risk in UB if the object passed to you is actually const. Namely, if you call any methods that actually mutate the object (which is very easy to do by accident now that you've cast away constness), you can easily get UB:
C++ standard, section § 5.2.11/7 [const cast]
[ Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a
const_cast that casts away a const-qualifier may produce undefined
behavior. —end note ]
It's not as bad in private methods/implementation functions because perhaps you carefully control how/when its called, but why do it this way? It's more dangerous to no benefit.
Conceptually, it's often the case that when you have a const and non-const version of the same function, you are just passing along internal references of the object (vector::operator[] is a canonical example), and not actually mutating anything, which means that it will be safe either way you write it. But it's still more dangerous to cast away the constness of the input; although you might be unlikely to mess it up yourself, imagine a team setting where you write it the wrong way around and it works fine, and then someone changes the implementation to mutate something, giving you UB.
In summary, in many cases it may not make a practical difference, but there is a correct way to do it that's strictly better than the alternative: add constness to the input, and remove constness from the output.
I have actually only ever seen your first version before, so from my experience it is the more common idiom.
The first version seems correct to me while the second version can result in undefined behavior if (A) you pass an actual const object to the function and (B) the long code writes to that object. Given that in the first case the compiler will tell you if you're trying to write to the object I would never recommend option 2 as it is. You could consider a standalone function that takes/returns const however.

Same object as const and non-const, what should I assume to behave correctly?

I have a function of the form
return_type function(const argument_type & a, argument_type & b);
If the same object is passed as a and b, can I proceed with the function (thereby potentially modifying it) or halt?
I don't have the knowledge about how the compiler assumptions propagate on that case.
My guess is that non-constness assumptions for it being passed as non-const b should suffice and allow the modifications, but it's a guess.
Most answers were about being able to modify a or b, or call non-const methods. I know that if I pass the same object as both const and non-const I can do that. My doubt lies more in state that I'm going to left the program when the function returns, and if it can brake some compiler assumptions, for example:
class A(){
int value;
}
void function1(const A & arg1);
void function2(A & arg1);
void function3(const A & arg1, A & arg2);
A a;
a.value=5;
function1(a);
if(a.value==5){/*...*/;} // the if can be optimized out to true, because [a] was const
a.value=5;
function2(a);
if(a.value==5){/*...*/;} //must be checked, [a] could have been modified
a.value=5;
function3(a, a);
if(a.value==5){/*...*/;} // ???
Most similar question I've found (but not really it)
Modifying self within const method via non const pointer to self
The problem with
return_type function(const argument_type & a, argument_type & b);
and a call function( o , o ) is that the function probably
modifies the object through formal argument b, while
at the same time expecting formal argument a to remain unchanged.
So you will probably break the assumptions of the function, resulting in e.g. incorrect results or a crash.
There is one function that by design often has this kind of signature, namely the copy assignment operator. Its constant formal argument a is the explicity provided argument, and its mutable formal argument (if one can call it that) is the implicit *this. It can either check for self-reference (aliasing of the arguments) or it can copy and swap.
In summary, whether you can safely call the function with the same object as argument a and as argument b depends on the function, but chances are that you will violate the function's assumptions, that it has as an implicit precondition that the formal arguments refer to different objects.
You decide if that function may be called with the same argument given twice.
Anyway, you should not try to determine if you get the same object twice, as long as the correctness of your algorithm does not depend on it or you gave no guarantees.
They are passed exactly the same way at run time, same reference/pointer to exactly the same thing.
The difference with const happens only at compile time. The compiler will make sure you are calling only const methods and not trying to modify properties of this const object, otherwise it throws a compilation error, because you are not supposed to modify it.
My guess is that non-constness assumptions for it being passed as non-const b should suffice and allow the modifications, but it's a guess.
Yes, function can call non-const methods via b but not via a.
Intuitively, this means that function can change an object passed in via the const reference a if the same object is also passed in via the non-const reference b.
In general, const only makes guarantees about what you can or cannot do with a const variable. There may be other variables which refer to the const variable, or, to objects within it, that may enable the const variable to be modified by a function that doesn't look as though it's modifying a const variable.
Coming back to your case, it's upto functions and classes at a higher level than your function to themselves enforce constraints about what can and cannot be modified using const parameters, member functions, etc.

C++: Why is const_cast evil?

I keep hearing this statement, while I can't really find the reason why const_cast is evil.
In the following example:
template <typename T>
void OscillatorToFieldTransformer<T>::setOscillator(const SysOscillatorBase<T> &src)
{
oscillatorSrc = const_cast<SysOscillatorBase<T>*>(&src);
}
I'm using a reference, and by using const, I'm protecting my reference from being changed. On the other hand, if I don't use const_cast, the code won't compile. Why would const_cast be bad here?
The same applies to the following example:
template <typename T>
void SysSystemBase<T>::addOscillator(const SysOscillatorBase<T> &src)
{
bool alreadyThere = 0;
for(unsigned long i = 0; i < oscillators.size(); i++)
{
if(&src == oscillators[i])
{
alreadyThere = 1;
break;
}
}
if(!alreadyThere)
{
oscillators.push_back(const_cast<SysOscillatorBase<T>*>(&src));
}
}
Please provide me some examples, in which I can see how it's a bad idea/unprofessional to use a const_cast.
Thank you for any efforts :)
Because you're thwarting the purpose of const, which is to keep you from modifying the argument. So if you cast away the constness of something, it's pointless and bloating your code, and it lets you break promises that you made to the user of the function that you won't modify the argument.
In addition, using const_cast can cause undefined behaviour. Consider this code:
SysOscillatorBase<int> src;
const SysOscillatorBase<int> src2;
...
aFieldTransformer.setOscillator(src);
aFieldTransformer.setOscillator(src2);
In the first call, all is well. You can cast away the constness of an object that is not really const and modify it fine. However, in the second call, in setOscillator you are casting away the constness of a truly const object. If you ever happen to modify that object in there anywhere, you are causing undefined behaviour by modifying an object that really is const. Since you can't tell whether an object marked const is really const where it was declared, you should just never use const_cast unless you are sure you'll never ever mutate the object ever. And if you won't, what's the point?
In your example code, you're storing a non-const pointer to an object that might be const, which indicates you intend to mutate the object (else why not just store a pointer to const?). That might cause undefined behaviour.
Also, doing it that way lets people pass a temporary to your function:
blah.setOscillator(SysOscillatorBase<int>()); // compiles
And then you're storing a pointer to a temporary which will be invalid when the function returns1. You don't have this problem if you take a non-const reference.
On the other hand, if I don't use const_cast, the code won't compile.
Then change your code, don't add a cast to make it work. The compiler is not compiling it for a reason. Now that you know the reasons, you can make your vector hold pointers to const instead of casting a square hole into a round one to fit your peg.
So, all around, it would be better to just have your method accept a non-const reference instead, and using const_cast is almost never a good idea.
1 Actually when the expression in which the function was called ends.
by using const, I'm protecting my reference from being changed
References can't be changed, once initialized they always refer to the same object. A reference being const means the object it refers to cannot be changed. But const_cast undoes that assertion and allows the object to be changed after all.
On the other hand, if I don't use const_cast, the code won't compile.
This isn't a justification for anything. C++ refuses to compile code that may allow a const object to be changed because that is the meaning of const. Such a program would be incorrect. const_cast is a means of compiling incorrect programs — that is the problem.
For example, in your program, it looks like you have an object
std::vector< SysOscillatorBase<T> * > oscillators
Consider this:
Oscillator o; // Create this object and obtain ownership
addOscillator( o ); // cannot modify o because argument is const
// ... other code ...
oscillators.back()->setFrequency( 3000 ); // woops, changed someone else's osc.
Passing an object by const reference means not only that the called function can't change it, but that the function can't pass it to someone else who can change it. const_cast violates that.
The strength of C++ is that it provides tools to guarantee things about ownership and value semantics. When you disable those tools to make the program compile, it enables bugs. No good programmer finds that acceptable.
As a solution to this particular problem, it looks likely that the vector (or whatever container you're using) should store the objects by value, not pointer. Then addOscillator can accept a const reference and yet the stored objects are modifiable. Furthermore, the container then owns the objects and ensures they are safely deleted, with no work on your part.
The use of const_cast for any reason other than adapting to (old) libraries where the interfaces have non-const pointers/references but the implementations don't modify the arguments is wrong and dangerous.
The reason that it is wrong is because when your interface takes a reference or pointer to a constant object you are promising not to change the object. Other code might depend on you not modifying the object. Consider for example, a type that holds an expensive to copy member, and that together with that it holds some other invariants.
Consider a vector<double> and a precomputed average value, the *average is updated whenever a new element is added through the class interface as it is cheap to update then, and if it is requested often there is no need to recompute it from the data every time. Because the vector is expensive to copy, but read access might be needed the type could offer a cheap accessor that returns a std::vector<double> const & for user code to check values already in the container. Now, if user code casts away the const-ness of the reference and updates the vector, the invariant that the class holds the average is broken and the behavior of your program becomes incorrect.
It is also dangerous because you have no guarantee that the object that you are passed is actually modifiable or not. Consider a simple function that takes a C null terminated string and converts that to uppercase, simple enough:
void upper_case( char * p ) {
while (*p) {
*p = ::to_upper(*p);
++p;
}
}
Now lets assume that you decide to change the interface to take a const char*, and the implementation to remove the const. User code that worked with the older version will also work with the new version, but some code that would be flagged as an error in the old version will not be detected at compile time now. Consider that someone decided to do something as stupid as upper_case( typeid(int).name() ). Now the problem is that the result of typeid is not just a constant reference to a modifiable object, but rather a reference to a constant object. The compiler is free to store the type_info object in a read-only segment and the loader to load it in a read-only page of memory. Attempting to change it will crash your program.
Note that in both cases, you cannot know from the context of the const_cast whether extra invariants are maintained (case 1) or even if the object is actually constant (case 2).
On the opposite end, the reason for const_cast to exist was adapting to old C code that did not support the const keyword. For some time functions like strlen would take a char*, even though it is known and documented that the function will not modify the object. In that case it is safe to use const_cast to adapt the type, not to change the const-ness. Note that C has support for const for a very long time already, and const_cast has lesser proper uses.
The const_cast would be bad because it allows you to break the contract specified by the method, i.e. "I shall not modify src". The caller expects the method to stick to that.
It's at least problematic. You have to distinguish two constnesses:
constness of the instantiated variable
This may result in physical constness, the data being placed in a read-only segment
constness of the reference parameter / pointer
This is a logical constness, only enforced by the compiler
You are allowed to cast away the const only if it's not physically const, and you can't determine that from the parameter.
In addition, it's a "smell" that some parts of your code are const-correct, and others aren't. This is sometimes unavoidable.
In your first example, you assign a const reference to what I assume is a non-const pointer. This would allow you to modify the original object, which requires at least a const cast. To illustrate:
SysOscillatorBase<int> a;
const SysOscillatorBase<int> b;
obj.setOscillator(a); // ok, a --> const a --> casting away the const
obj.setOscilaltor(b); // NOT OK: casting away the const-ness of a const instance
Same applies to your second example.
, while I can't really find the reason why const_cast is evil.
It is not, when used responsibily and when you know what you're doing. (Or do you seriously copy-paste code for all those methods that differ only by their const modifier?)
However, the problem with const_cast is that it can trigger undefined behavior if you use it on variable that originally was const. I.e. if you declare const variable, then const_cast it and attempt to modify it. And undefined behavior is not a good thing.
Your example contains precisely this situation: possibly const variable converted into non-const. To avoid the problem store either const SysOscillatorBase<T>*(const pointer) or SysOscillatorBase<T> (copy) in your object list, or pass reference instead of const reference.
You are violating a coding contract. Marking a value as const is saying you can use this value but never change it. const_cast breaks this promise and can create unexpected behaviour .
In the examples you give, it seems your code is not quite right. oscillatorSrc should probably be a const pointer, although if you really do need to change the value then you should not pass it in as a const at all.
Basicly const promises you and the compiler that you will not change the value. The only time you should use when you use a C library function (where const didn't exist), that is known not to change the value.
bool compareThatShouldntChangeValue(const int* a, const int* b){
int * c = const_cast<int*>(a);
*c = 7;
return a == b;
}
int main(){
if(compareThatShouldntChangeValue(1, 7)){
doSomething();
}
}
You probably need to define you container as containing const objects
template <typename T> struct Foo {
typedef std::vector<SysOscillator<T> const *> ossilator_vector;
}
Foo::ossilator_vector<int> oscillators;
// This will compile
SysOscillator<int> const * x = new SysOscillator<int>();
oscillators.push_back(x);
// This will not
SysOscillator<int> * x = new SysOscillator<int>();
oscillators.push_back(x);
That being said if you have no control over the typedef for the container maybe it
is ok to const_cast at the interface between your code and the library.

Why can't a function have a reference argument in C?

For example: void foo( int& i ); is not allowed. Is there a reason for this, or was it just not part of the specification? It is my understanding that references are generally implemented as pointers. In C++, is there any functional difference (not syntactic/semantic) between void foo( int* i ) and void foo( int& i )?
Because references are a C++ feature.
References are merely syntactic vinegar for pointers. Their implementation is identical, but they hide the fact that the called function might modify the variable. The only time they actually fill an important role is for making other C++ features possible - operator overloading comes to mind - and depending on your perspective these might also be syntactic vinegar.
For example: void foo( int& i ); is not allowed. Is there a reason for this, or was it just not part of the specification?
It was not a part of the specification. The syntax "type&" for references were introduced in C++.
It is my understanding that references are generally implemented as pointers. In C++, is there any functional difference (not syntactic/semantic) between void foo( int* i ) and void foo( int& i )?
I am not sure if it qualifies as a semantic difference, but references offer better protection against dereferencing nulls.
Because the & operator has only 2 meanings in C:
address of its operand (unary),
and, the bitwise AND operator (binary).
int &i; is not a valid declaration in C.
For a function argument, the difference between pointer and reference is not that big a deal, but in many cases (e.g. member variables) having references substantially limits what you can do, since it cannot be rebound.
References were not present in C. However, C did have what amounts to mutable arguments passed by reference. Example:
int foo(int in, int *out) { return (*out)++ + in; }
// ...
int x = 1; int y = 2;
x = foo(x, &y);
// x == y == 3.
However, it was a common error to forget to dereference "out" in every usage in more complicated foo()s. C++ references allowed a smoother syntax for representing mutable members of the closure. In both languages, this can confound compiler optimizations by having multiple symbols referring to the same storage. (Consider "foo(x,x)". Now it's undefined whether the "++" occurs after only "*out" or also after "in", since there's no sequence point between the two uses and the increment is only required to happen sometime after the value of the left expression is taken.)
But additionally, explicit references disambiguate two cases to a C++ compiler. A pointer passed into a C function could be a mutable argument or a pointer to an array (or many other things, but these two adequately illustrate the ambiguity). Contrast "char *x" and "char *y". (... or fail to do so, as expected.) A variable passed by reference into a C++ function is unambiguously a mutable member of the closure. If for instance we had
// in class baz's scope
private: int bar(int &x, int &y) {return x - y};
public : int foo(int &x, int &y) {return x + bar(x,y);}
// exit scope and wander on ...
int a = 1; int b = 2; baz c;
a = c.foo(a,b);
We know several things:
bar() is only called from foo(). This means bar() can be compiled so that its two arguments are found in foo()'s stack frame instead of it's own. It's called copy elision and it's a great thing.
Copy elision gets even more exciting when a function is of the form "T &foo(T &)", the compiler knows a temporary is going in and coming out, and the compiler can infer that the result can be constructed in place of the argument. Then no copying of the temporary in or the result out need be compiled in. foo() can be compiled to get its argument from some enclosing stack frame and write its result directly to some enclosing stack frame.
a recent article about copy elision and (surprise) it works even better if you pass by value in modern compilers (and how rvalue references in C++0x will help the compilers skip even more pointless copies), see http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ .