Imagine the following simplified code:
#include <iostream>
void foo(const int& x) { do_something_with(x); }
int main() { foo(42); return 0; }
(1) Optimizations aside, what happens when 42 is passed to foo
?
Does the compiler stick 42 somewhere (on the stack?) and pass its address to foo
?
(1a) Is there anything in the standard that dictates what is to be done in this situation (or is it strictly up to the compiler)?
Now, imagine slightly different code:
#include <iostream>
void foo(const int& x) { do_something_with(x); }
struct bar { static constexpr int baz = 42; };
int main() { foo(bar::baz); return 0; }
It won't link, unless I define int bar::baz;
(due to ODR?).
(2) Besides ODR, why can't the compiler do whatever it did with 42 above?
An obvious way to simplify things is to define foo
as:
void foo(int x) { do_something_with(x); }
However, what would one do in case of a template? Eg:
template<typename T>
void foo(T&& x) { do_something_with(std::forward<T>(x)); }
(3) Is there an elegant way to tell foo
to accept x
by value for primitive types? Or do I need to specialize it with SFINAE or some such?
EDIT: Modified what happens inside foo
as it's irrelevant to this question.
When you pass by const reference, you take the argument in by reference (avoiding making any copies of it), but cannot make any changes to the original object (much as would happen when you would take the parameters in by value).
A non-const reference cannot point to a literal. You cannot bind a literal to a reference to non-const (because modifying the value of a literal is not an operation that makes sense) and only l-values can be bound to references to non-const.
Such a reference is called an lvalue reference to a const value (sometimes called a reference to const or a const reference). In the above program, we bind const reference ref to modifiable lvalue x . We can then use ref to access x , but because ref is const, we can not modify the value of x through ref .
The data type const string& literally means “a reference to a string object whose contents will not be changed.” 1. Pass by value - a copy of the original object is created and passed. To pass a string by value, you just use the data type string.
Does the compiler stick 42 somewhere (on the stack?) and pass its address to
foo
?
A temporary object of type const int
is created, initialized with the prvalue expression 42
, and bound to the reference.
In practice, if foo
is not inlined, that requires allocating space on the stack, storing 42
into it, and passing the address.
Is there anything in the standard that dictates what is to be done in this situation (or is it strictly up to the compiler)?
[dcl.init.ref].
Besides ODR, why can't the compiler do whatever it did with 42 above?
Because according to the language, the reference is bound to the object bar::baz
, and unless the compiler knows exactly what foo
is doing at the point where it is compiling the call, then it has to assume that this is significant. For example, if foo
contains an assert(&x == &bar::baz);
, that must not fire with foo(bar::baz)
.
(In C++17, baz
is implicitly inline as a constexpr
static data member; no separate definition is required.)
Is there an elegant way to tell
foo
to acceptx
by value for primitive types?
There is generally not much point in doing this in the absence of profiling data showing that pass-by-reference is actually causing problems, but if you really need to do it for some reason, adding (possibly SFINAE-constrained) overloads would be the way to go.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With