Bob said:
Most languages support the concept of passing a constant value by reference
simply by specifying including a constant expression in the call syntax. In
"good old C", passing a pointer was as close as you could get to passing a value
"by reference", but C and C++ seem to insist that the code must explicitly
declare storage for the constant in order to obtain a pointer to it.
This is because neither C nor C++ actually *have* call-by-reference, whether
you're talking constants or not. C++ has the rather muddled concept of
passing references by value, which is almost but not quite the same thing.
All I'm asking is how to get the compiler to recognize that I'm trying to
pass the address of a constant and to have the compiler allocate the
constant for me.
That's just not in the language(s).
The C/C++ compiler happily does this with string constants, but not
numeric constants. (And don't start in with "yeah but strings are really
pointers". It's still a constant expression, and the compiler allocates
storage for the constant.)
What can I say? C and C++ are weird like that. You need to really get the
terminology straight to see where they're coming from, and strings are very
special.
"This is a string constant" is not a string constant. There's no such thing
as a "string constant" as far as the language is concerned. It's a string
literal. String literals are constant expressions which evaluate to constant
char arrays. And those arrays, in turn, decay to char pointers. This is how
you get your free "address to a constant", and it requires cooperation from
the decidedly arcane way that strings and arrays work in C and C++.
There's no such thing as a "numeric constant" in the language either. All
you have is constant expressions. Unlike arrays, which implicitly decay into
pointers, there's no way to get a pointer from a non-array expression
without assigning it to a named object, since you cannot take the address of
an expression.
C++ introduces the concept of anonymous temporaries so you can bind these to
constant references, but you cannot take their address directly. Now, typed
references are safe:
void foo(const int& a);
foo(5 + 3);
but passing an int by reference is rather pointless, as you could just pass
it by value. C++ generally eschews void* and uses templates instead to
achieve genericity:
template<typename T>
void foo(const T& t) {
...
}
foo(5 + 3);
foo(std::string("Hello, ") + std::string("world! "));
In both cases temporaries are constructed and bound to references. Now you
can even do this unsafely and get pointers by using this mechanism as an
intermediary step:
void bar(const void* v) {
// use v here but don't store it, as it'll become invalid soon!
}
template<typename T>
void foo(const T& t) {
bar(&t);
}
foo(5 + 3);
foo(std::string("Hello, ") + std::string("world! "));
The reason this works is that the temporary you pass to foo() actually has
storage when foo() calls bar(), namely as the parameter "t".
Now, be careful about that last call: what bar() actually receives is a
pointer to an std::string object, not a char*. And when we do this:
foo((std::string("Hello, ") + std::string("world! ")).c_str());
What happens *exactly* is:
- The compiler generates a temporary for the "Hello, " string.
- The compiler generates a temporary for the "world! " string.
- The compiler invokes operator+ for the temporaries, the result being
another temporary.
- The compiler invokes c_str() on that temporary, getting a const char*.
- It binds this const char* to a reference, passing that reference to foo(),
which is thus receiving a const char*&.
- foo() takes the address of this reference, yielding a const char* const*,
which is passed to bar().
And *none of this* actually involves call-by-reference! It's a wonder
programmers' heads don't explode more often.