What's the Semantically Accurate Position for the Ampersand in C++ References

What's the semantically accurate position for the ampersand in C++ references

While researching for this question, I already found the answer:

The & needs to be written just like the *.

The demonstration code is similar to the pointer demonstration code:

int main() {
int a = 0;
int b = 1;

int& ar = a, br = b;

br = 2;

return b;
}

This returns 1, which means that ar is an int reference, while br is just an integer.

Where ampersand & can be put when passing argument by reference?

Both are exactly the same. No difference at all.

All that matters is that & should be between the type and the variable name. Spaces don't matter.

So

void AddOne(int&  y);
void AddOne(int &y);
void AddOne(int & y)
void AddOne(int & y);
void AddOne(int&y);

are same!

What do the ampersand '&' and star '*' symbols mean in Rust?

Using * to dereference a reference wouldn't be correct in C++. So I'd like to understand why this is correct in Rust.

A reference in C++ is not the same as a reference in Rust. Rust's references are much closer (in usage, not in semantics) to C++'s pointers. With respect to memory representation, Rust's references often are just a single pointer, while C++'s references are supposed to be alternative names of the same object (and thus have no memory representation).

The difference between C++ pointers and Rust references is that Rust's references are never NULL, never uninitialized and never dangling.


The Add trait is implemented (see the bottom of the doc page) for the following pairs and all other numeric primitives:

  • &i32 + i32
  • i32 + &i32
  • &i32 + &i32

This is just a convenience thing the std-lib developers implemented. The compiler can figure out that a &mut i32 can be used wherever a &i32 can be used, but that doesn't work (yet?) for generics, so the std-lib developers would need to also implement the Add traits for the following combinations (and those for all primitives):

  • &mut i32 + i32
  • i32 + &mut i32
  • &mut i32 + &mut i32
  • &mut i32 + &i32
  • &i32 + &mut i32

As you can see that can get quite out of hand. I'm sure that will go away in the future. Until then, note that it's rather rare to end up with a &mut i32 and trying to use it in a mathematical expression.

What does T&& (double ampersand) mean in C++11?

It declares an rvalue reference (standards proposal doc).

Here's an introduction to rvalue references.

Here's a fantastic in-depth look at rvalue references by one of Microsoft's standard library developers.

CAUTION: the linked article on MSDN ("Rvalue References: C++0x Features in VC10, Part 2") is a very clear introduction to Rvalue references, but makes statements about Rvalue references that were once true in the draft C++11 standard, but are not true for the final one! Specifically, it says at various points that rvalue references can bind to lvalues, which was once true, but was changed.(e.g. int x; int &&rrx = x; no longer compiles in GCC) – drewbarbs Jul 13 '14 at 16:12

The biggest difference between a C++03 reference (now called an lvalue reference in C++11) is that it can bind to an rvalue like a temporary without having to be const. Thus, this syntax is now legal:

T&& r = T();

rvalue references primarily provide for the following:

Move semantics. A move constructor and move assignment operator can now be defined that takes an rvalue reference instead of the usual const-lvalue reference. A move functions like a copy, except it is not obliged to keep the source unchanged; in fact, it usually modifies the source such that it no longer owns the moved resources. This is great for eliminating extraneous copies, especially in standard library implementations.

For example, a copy constructor might look like this:

foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}

If this constructor were passed a temporary, the copy would be unnecessary because we know the temporary will just be destroyed; why not make use of the resources the temporary already allocated? In C++03, there's no way to prevent the copy as we cannot determine whether we were passed a temporary. In C++11, we can overload a move constructor:

foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}

Notice the big difference here: the move constructor actually modifies its argument. This would effectively "move" the temporary into the object being constructed, thereby eliminating the unnecessary copy.

The move constructor would be used for temporaries and for non-const lvalue references that are explicitly converted to rvalue references using the std::move function (it just performs the conversion). The following code both invoke the move constructor for f1 and f2:

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

Perfect forwarding. rvalue references allow us to properly forward arguments for templated functions. Take for example this factory function:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}

If we called factory<foo>(5), the argument will be deduced to be int&, which will not bind to a literal 5, even if foo's constructor takes an int. Well, we could instead use A1 const&, but what if foo takes the constructor argument by non-const reference? To make a truly generic factory function, we would have to overload factory on A1& and on A1 const&. That might be fine if factory takes 1 parameter type, but each additional parameter type would multiply the necessary overload set by 2. That's very quickly unmaintainable.

rvalue references fix this problem by allowing the standard library to define a std::forward function that can properly forward lvalue/rvalue references. For more information about how std::forward works, see this excellent answer.

This enables us to define the factory function like this:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}

Now the argument's rvalue/lvalue-ness is preserved when passed to T's constructor. That means that if factory is called with an rvalue, T's constructor is called with an rvalue. If factory is called with an lvalue, T's constructor is called with an lvalue. The improved factory function works because of one special rule:

When the function parameter type is of
the form T&& where T is a template
parameter, and the function argument
is an lvalue of type A, the type A& is
used for template argument deduction.

Thus, we can use factory like so:

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)

Important rvalue reference properties:

  • For overload resolution, lvalues prefer binding to lvalue references and rvalues prefer binding to rvalue references. Hence why temporaries prefer invoking a move constructor / move assignment operator over a copy constructor / assignment operator.
  • rvalue references will implicitly bind to rvalues and to temporaries that are the result of an implicit conversion. i.e. float f = 0f; int&& i = f; is well formed because float is implicitly convertible to int; the reference would be to a temporary that is the result of the conversion.
  • Named rvalue references are lvalues. Unnamed rvalue references are rvalues. This is important to understand why the std::move call is necessary in: foo&& r = foo(); foo f = std::move(r);

New Features in C++ - what does this code mean

There are many "new" features used in this declaration:

  • templates (template keyword; see https://en.wikipedia.org/wiki/Template_(C%2B%2B))
  • variadic templates (the ... argument; see https://en.wikipedia.org/wiki/Variadic_template)
  • smart pointers (unique_ptr, see https://en.wikipedia.org/wiki/Smart_pointer)
  • rvalue references (Args&&, see https://en.wikipedia.org/wiki/C%2B%2B11#Rvalue_references_and_move_constructors)

Basically the code means "declare a template for functions with an arbitrary number of parameters of any type and return a unique_ptr specialised for the given type T". In addition the rvalue reference (&&) tells you that the parameters will be moved instead of copied.

In short: make_unique<Type>(v) is basically the same as unique_ptr<Type>(new Type(v)).

C++ - What does it mean when you put an ampersand (&) in front of an rvalue reference?

It's simply checking for self-assignment. If the address of the object being passed in is the same as this then do nothing.

C++ functions: ampersand vs asterisk

Pointers (ie. the '*') should be used where the passing "NULL" is meaningful.
For example, you might use a NULL to represent that a particular object needs to be created, or that a particular action doesn't need to be taken.
Or if it ever needs to be called from non-C++ code. (eg. for use in shared libraries)

eg. The libc function time_t time (time_t *result);

If result is not NULL, the current time will be stored. But if result is NULL, then no action is taken.

If the function that you're writing doesn't need to use NULL as a meaningful value then using references (ie. the '&') will probably be less confusing - assuming that is the convention that your project uses.



Related Topics



Leave a reply



Submit