References, simply

References are for parameter passing, including range-for. Sometimes they’re useful as local variables, but pointers or structured bindings are usually better. Any other use of references typically leads to endless design debates. This post is an attempt to shed light on this situation, and perhaps reduce some of the time spent on unresolved ongoing design debates in the C++ community. Thank you to the following for their feedback on drafts of this material: Howard Hinnant, Arthur O’Dwyer, Richard Smith, Bjarne Stroustrup, Ville Voutilainen.

Edited to add: mention the core language specification complexity, and that the list of examples is not exhaustive and other examples fall into the same categories as listed examples.


References

What references are and how to use them

In C++, a C& or C&& reference is an indirect way to refer to an existing object. Every reference has a dual nature: It’s implemented under the covers as a pointer, but semantically it usually behaves like an alias because most uses of its name automatically dereference it. (Other details are not covered here, including the usual parameter passing rules and that C&& has a different meaning depending on whether C is a concrete type or a template parameter type.)

C++ references were invented to be used as function parameter/return types, and that’s what they’re still primarily useful for. Since C++11, that includes the range-for loop which conceptually works like a function call (see Q&A).

Sometimes, a reference can also be useful as a local variable, though in modern C++ a pointer or structured binding is usually better (see Q&A).

That’s it. All other uses of references should be avoided.


Advanced note for experts

Please see the Q&A for more, including const& lifetime extension, pair<T&, U&>, optional<T&>, and other cases. Note that the examples explicitly listed below are not intended to be exhaustive; other examples (e.g., tie, reference_wrapper) fall under one or more of the cases listed below.





Appendix: Q&A

Historical question: Can you elaborate a little more on why references were invented for function parameter/return types?

Here is a summary, but for more detail please see The Design and Evolution of C++ (D&E) section 3.7, which begins: “References were introduced primarily to support operator overloading…”

In C, to pass/return objects to/from functions you have two choices: either pass/return a copy, or take their address and pass/return a pointer which lets you refer to an existing object.

Neither is desirable for overloaded operators. There are two motivating use cases, both described in D&E:

  • The primary use case is that we want to pass an existing object to an operator without copying it. Passing by reference lets calling code write just a - b, which is natural and consistent with built-in types’ operators. If we had to write &a - &b to pass by pointer, that would be (very) inconvenient, inconsistent with how we use the built-in operators, and a conflict when that operator already has a different meaning for raw pointers as it does in this example.
  • Secondarily, we want to return an existing object without copying it, especially from operators like unary * and []. Passing by reference lets calling code write str[0] = 'a'; which is natural and consistent with built-in arrays and operators. If we had to write *str[0] = 'a'; to return by pointer, that would be (slightly) inconvenient and also inconsistent with built-in operators, but not the end of the world and so this one is only a secondary motivating case.

Those are the only uses of references discussed in D&E, including in the section on smart references and operator., and the only places where references are really needed still today.

What was that about range-for being like a function call?

The C++11 range-for loop is semantically like function parameter passing: We pass a range to the loop which takes it as if by an auto&& parameter, and then the loop passes each element in turn to each loop iteration and the loop body takes the element in the way it declares the loop element variable. For example, this loop body takes its element parameter by const auto&:

// Using range-for: The loop variable is a parameter to
// the loop body, which is called once per loop iteration
for (const auto& x : rng) { ... }

If we were instead using the std::for_each algorithm with the loop body in a lambda, the parameter passing is more obvious: for_each takes the range via an iterator pair of parameters, and then calls the loop body lambda passing each element as an argument to the lambda’s parameter:

// Using std::for_each: Basically equivalent
for_each (begin(rng), end(rng), [&](const auto& x) { ... });

Is a reference a pointer to an object, or an alternate name for the object?

Yes — it is either or both, depending on what you’re doing at the moment.

This dual nature is the core problem of trying to use a reference as a general concept: Sometimes the language treats a reference as a pointer (one level of indirection), and sometimes it treats it as an alias for the referenced object (no level of indirection, as if it were an implicitly dereferenced pointer), but those are not the same thing and references make those things visually ambiguous.

When passing/returning an object by reference, this isn’t a problem because we know we’re always passing by pointer under the covers and when we use the name we’re always referring to the existing object by alias. That’s clear, and references are well designed for use as function parameter/return types.

But when trying to use references elsewhere in the language, we have to know which aspect (and level of indirection) we’re dealing with at any given time, which leads to confusion and woe. References have never been a good fit for non-parameter/return uses. And that is doubly sad, because supporting “reference types” throughout the language complicates the C++ core language specification, while simultaneously forcing us to keep teaching why to avoid using nearly all of that generalized language support.

Aren’t local references useful because of lifetime extension?

We “made it useful” as an irregular extension, but that’s brittle and now basically unnecessary as of C++17.

A brief history of lifetime extension: After references were first added in the 1980s, C++ later added a special case where binding a temporary object to a local variable of type const& and still later auto&& (but not generally other kinds of local references) was “made useful” by imbuing only those references with the special power of extending the lifetime of a temporary object, just because we could (and because there were use cases where it was important for performance, before C++17 guaranteed copy elision). However, these cases have always been:

  • brittle and inconsistent (e.g., const T& t = f(); and const T& t = f().x; and struct X { const T& r; } x = { f() }; extend the lifetime of an object returned by value from f(), but const T& t = f().g(); does not);
  • irregular (e.g., T& t = f(); is ill-formed, whereas const T& t = f(); and T t = f(); still uniformly work); and
  • unnecessary now that C++17 has guaranteed copy elision (e.g., just write T t = f(); and the meaning is both obvious and correct, as well as way easier to teach and learn and use).

Aren’t local references useful to get meaningful names for parts of an object returned from a function?

Yes, but since C++17 structured bindings are strictly better.

For example, given a set<int> s and calling an insert function that returns a pair<iterator, bool>, just accessing the members of the pair directly means putting up with hard-to-read code:

// accessing the members of a pair directly (unmeaningful names)
auto value = s.insert(4);
if (value.second) {
    do_something_with(value.first);
}

Structured bindings lets us directly name the members — note that this just invents names for them, it does not create any actual pointer indirection:

// using structured bindings (easy to use meaningful names)
auto [position, succeeded] = s.insert(4);
if (succeeded) {
    do_something_with(position);
}

In the olden days before structured bindings, some people like to use references to indirectly name the members — which like the above gives them readable names, but unlike the above does create new pointer-equivalent indirect variables and follows those pointers which can incur a little space and time overhead (and also isn’t as readable)…

// using references (cumbersome, don't do this anymore)
auto value      = s.insert(4);
auto& position  = value.first;          // equivalent to pointers
auto& succeeded = value.second;
if (succeeded) {                        // invisible dereference
    do_something_with(position);        // invisible dereference
}

// or using pointers (ditto)
auto value     = s.insert(4);
auto position  = &value.first;          // self-documenting pointers
auto succeeded = &value.second;
if (*succeeded) {                       // visible dereference
    do_something_with(*position);       // visible dereference
}

… but even in the olden days, references were never significantly better than using pointers since the code is basically identical either way. Today, prefer structured bindings.

Aren’t local references useful to express aliases, for example to a member of an array or container?

Yes, though pointers can do it equivalently, it’s a style choice.

For example, this local reference is useful:

auto& r = a[f(i)];
// ... then use r repeatedly ...

Or you can equivalently use a pointer:

auto p = &a[f(i)];
// ... then use *p repeatedly ...

Isn’t T& convenient for easily expressing a pointer than can’t be rebound to another object?

Yes, though T* const does equally well.

Either is mainly useful as a local variable. (See also previous answer.)

Isn’t T& convenient for easily expressing a pointer that is not null?

Not exactly — T& lets you express a pointer that’s not-null and that can’t be rebound.

You can also express not-null by using gsl::not_null<> (see for example the Microsoft GSL implementation), and one advantage of doing it this way is that it also lets you independently specify whether the pointer can be rebound or not — if you want it not to be rebindable, just add const as usual.

What about lambda [&] capture?

[&] is the right default for a lambda that’s passed to a function that will just use it and then return (aka structured lifetime) without storing it someplace where it will outlive the function call. Those structured uses fall under the umbrella of using references as parameter/return types. For non-parameter/return uses, prefer using pointers.

What about pair<T&, U&> and tuple<T&, U&> and struct { T& t; U& u; }?

I’ve mainly seen these come up as parameter and return types, where for the struct case the most common motivation is that C++ doesn’t (yet) support multiple return values, or as handwritten equivalents of what lambda [&] capture does. For those uses, they fall under the umbrella of using references as parameter/return types. For non-parameter/return uses, prefer using pointers.

[GENERAL UMBRELLA QUESTION] But what about using a reference for ((other use not as a parameter or return type or local variable))?

Don’t. WOPR said it best, describing something like the game of trying to answer this class of question: “A strange game. The only winning move is not to play.”

Don’t let yourself be baited into even trying to answer this kind of question. For example, if you’re writing a class template, just assume (or document) that it can’t be instantiated with reference types. The question itself is a will o’ the wisp, and to even try to answer it is to enter a swamp, because there won’t be a general reasonable answer.

(Disclaimer: You, dear reader, may at this very moment be thinking of an ((other use)) for which you think you have a reasonable and correct answer. Whatever it is, it’s virtually certain that a significant fraction of other experts are at this very moment reading this and thinking of that ((other use)) with a different answer, and that you can each present technical arguments why the other is wrong. See optional<T&> below.)

All of the remaining questions are specific cases of this general umbrella question, and so have the same answer…

… But what about using a reference type as a class data member?

For the specific case of pair<T&, U&> and tuple<T&, U&> and struct { T& t; U& u; }, see the earlier answer regarding those. Otherwise:

Don’t, see previous. People keep trying this, and we keep having to teach them not to try because it makes classes work in weird and/or unintended ways.

Pop quiz: Is struct X { int& i; }; copyable? If not, why not? If so, what does it do?

Basic answer: X is not copy assignable, because i cannot be modified to point at something else. But X is copy constructible, where i behaves just as if it were a pointer.

Better answer: X behaves the same as if the member were int* const i; — so why not just write that if that’s what’s wanted? Writing a pointer is arguably simpler and clearer.

… But what about using a reference type as an explicit template argument?

Don’t, see above. Don’t be drawn into trying to answer when this could be valid or useful.

Explicitly jamming a reference type into a template that didn’t deduce it and isn’t expecting it, such as calling std::some_algorithm<std::vector<int>::iterator&>(vec.begin(), vec.end());, will be either very confusing or a compile-time error (or both, a very confusing compile-time error — try std::sort).

… But what about using a reference type for a class template specialization?

Don’t, see above. Don’t be drawn into trying to answer when this could be valid or useful.

… But wait, not even optional<T&>?

Don’t, see above. Especially not this one.

An astonishing amount of ink has been spilled on this particular question for years, and it’s not slowing down — the pre-Prague mailing had yet another paper proposing an optional<T&> as one alternative, and we’ve had multiple Reddit’d posts about it in the past few weeks (exampleexample). Those posts are what prompted me to write this post, expanding on private email I wrote to one of the authors.

Merely knowing that the discussion has continued for so many years with no consensus is a big red flag that the question itself is flawed. And if you’re reading this and think you have answer, ask yourself whether in your answer optional<T&> really IS-AN optional<T> — template specializations should be substitutable for the primary template (ask vector<bool>) and the proposed answers I’ve seen for optional<T&> are not substitutable semantically (you can’t write generic code that uses an optional<T> and works for that optional<T&>), including that some of them go so far as actually removing common functions that are available on optional<T> which clearly isn’t substitutable.

There’s a simple way to cut this Gordian knot: Simply knowing that references are for parameter/return types will warn us away from even trying to answer “what should optional<T&> do?” as a design trap, and we won’t fall into it. Don’t let yourself be baited into trying to play the game of answering what it should mean. “The only winning move is not to play.”

Use optional<T> for values, and optional<T*> or optional<not_null<T*>> for pointers.

Epilogue: But wait, what about ((idea for optional<T&>))?

If after all the foregoing you still believe you have a clear answer to what optional<T&> can mean that:

  • is still semantically IS-A substitutable for the optional<> primary template (e.g., generic code can still use it as a more general optional);
  • cannot be represented about equally well by optional<not_null<T*>>; and
  • does not already have published technical arguments against it showing problems with the approach;

then please feel free to post a link below to a paper that describes that answer in detail.

Fair warning, though: Even while reviewing this article, a world-class expert reviewer responded regarding experience with one of the world’s most popular versions of optional<T&>:

“I know that Boost has optional<T&> so I tried it for my use case … ((code example)) is a run-time error for me. I expected ((a different behavior)) and it did not. I suspect the mistake is in the ambiguity: Does assigning an optional<T&> assign through the reference, or rebind the reference?”

My answer: Exactly, the dual nature of references is always the problem.

  • If the design embraces the pointer-ness of references (one level of indirection), then one set of use cases works and people with alias-like use cases get surprised.
  • If the design embraces the alias-ness of references (no indirection), then the other set of use cases works and people with pointer-like use cases get surprised.
  • If the design mixes them, then a variety of people get surprised in creative ways.

Java object references encounter similar problems — everything is implicitly a pointer, but there’s no clean way to syntactically distinguish the pointer vs. the pointee. Being able to talk separately about the pointer vs. the pointee is an important distinction, and an important and underestimated advantage of the Pointer-like things (e.g., raw pointers, iterators, ranges, views, spans) we have in C++.

18 thoughts on “References, simply

  1. I’m a little confused by the position against local references. Take the following example:

    std::unordered_map<int, MyClass> my_map;
    ...
    const auto it = m.find(i);
    if (it != m.end())
    {
        const auto& my_class_instance = it->second;
        ...
    }
    

    It sounds like you’re suggesting that local would be better as a const gsl::not_null.

  2. @John I think he suggests it would be better as:

    const auto [key, my_class_instance] = *it;

    Explaining that “Structured bindings lets us directly name the members — note that this just invents names for them, it does not create any actual pointer indirection”.

  3. @Panos
    If that’s the case my personal and of course very subjective opinion is:
    [More readable]
    const auto& my_class_instance = it->second;

    [Less readable + we “get something (the key)” that we are not interesting in using]
    const auto [key, my_class_instance] = *it;

  4. I think structured bindings would be more ergonomic if identifiers could be optionally omitted.
    Then you won’t have to give a throwaway name to something you aren’t going to use.
    Commas of course can’t be omitted because binding is positional.

  5. I would like to argue that references provide the benefits of:
    – requiring no need to, well, dereference anything to access the data, and less typing means less mistakes, and
    – more obvious object availability requirements.
    For instance, in the example you mentioned “Is struct X { int& i; }; copyable?” if “int const * i” is used, it is possible to initialize object with a null pointer, enforcing at least one check in constructor to verify that is not the case to make object usable, while references do not allow that. Using a pointer or a library wrapper may hide away this check, but it has to be performed at some point in runtime. Also said struct X can have move-only behavior by design if that is the issue.

  6. How do you do the following without references?

    
    SerialPort serial(...);
    PortReader (serial, ...); // serial passed as reference
    PortSender( serial, ...); // serial passed as reference
    
    // invoke the reader and sender as separate threads with appropriate locks, etc in SerialPort
    
    

    Presumably the suggestion would be to use a smart pointer instead of a reference but why is that better?

    More generally, I don’t think the comment that the use of reference vs pointer is stylistic in the case of:

    
    auto& r = a[f(i)];
    // ... then use r repeatedly ...
    
    auto p = &a[f(i)];
    // ... then use *p repeatedly ...
    

    One of the motivations for decades has been to eliminate the problems of pointers. Doesn’t that suggest that the use of de-referencing operators should be discouraged at all times?

  7. @Panos
    Two issues with that structured binding option:

    1) Maybe I don’t want to copy my_class_instance (the structured binding could be declared as const auto&, but then you’re back to a local reference).
    2) I’d rather not introduce extra names that I’m not going to use.

  8. There is one important property of references which was completely ignored in this article. References are the only c++ entity that properly respect constness by propagating it down to the refered object. Therefore:
    [Code]
    const auto& xxx = getX();
    const gsl::not_null yyy = getX();
    [/Code]
    So not_null simply can’t replace reference as it requires specify the type even in c++17 with deduction rules. You may say that getX should return const ref so deduction rules should work, but it can return a value instead forcing client code to be changed in case of not_null. Moreover I saw so many code that returns mutable reference even when getX is const internationaly keeping returned object by (smart)pointer. So
    [Code]
    const gsl::not_null yyy = getX();
    [\code]
    doesn’t restrict mutation of the refered object (in bad code) where is ref version does. There is nothing in c++ that can replace local const ref and this idiom is very useful and wide spread.

  9. It’s a good writeup, I agree with most of it and really like the second part.
    Here I just point out where my approach differs.

    I teach references through the “alias” approach. My starting words are like “First thing to remember: reference is NOT an object! It is just a NAME you assign to an already existing object. Some teach it as a special flavor of pointer, as pointer in disguise. Unlearn that. Forget that. Pointer IS an object in C++. It is used for a plenty of interesting use cases. Keep those in your mind as well separated an unrelated.”
    And IME that works pretty well in practice. It just prevents “stupid” questions on what happens when you copy a reference: that immediately gets the “trick question” qualification as you can only copy objects. Or what happens if you take the address.
    And helps in thinking about the program in general.

    From this follows my disagreement on local aliasing: in the world of application writing it is pretty common to have deep collections and name items sitting in them. auto& is the tool for that. and the pointer alternative is forbidden as an alternative — pointers are only allowed when you have a case they are actually needed for manipulation.
    A related disagreement on auto const T = foo(); vs. auto const& T = foo(); The arguments with copy elision only hold for the case where foo returns rvalue. In too many application contexts the caller is not interested in that detail and want only capture the whatever is returned, as is. And while it is true that we van shoot ourselves with a f().g() like case, that is IME not a common thing to write. And for some time we get consistent warnings from our checking tools too. I had a few accidents related to miss the lifetime extensions, they all had someone “storing” the ref or address in a member involved.

  10. Can’t pass spending more ink on this one as I have previously commented on one of the Reddit threads about optional.

    > C++ references were invented to be used as function parameter/return types…
    Optional is a parameter/return type.

    So if you Ok with pair and tuple and struct { T& t; U& u; } as return type, how Optional is different?

    And yes, here is my example/value for “But wait, what about ((idea for optional))” template:
    If we agree that not_null makes code cleaned – and it does – we probably also agree that use of Optional clearly communicate that a value produced by a function may not be there.
    So for a case of type, like `map` that allows effectively search operation – there may not be value produces for some key. This is best expressed as:
    `map::find(:key) -> optional`

    However, users of a container might want to be able to modify result of a find / not have a copy of what is already is in a container. So this is what T& is for, right?

    Currently the role of Optional for std::map is played by an iterator, which leads to pretty inconvenient comparison with map::end().
    Note, that dereferencing map iterator produces a reference to a value, not a pointer. And so returning Optional won’t do here either without code like:
    “`
    auto maybeT = map.find(key);
    if (maybeT) {
    T& t = **maybeT;

    }

    “`
    This is not possible, according to the above advice of not using optinal “because we don’t have an agreement of what it actually means”.
    I managed to rich a solution that works for me: https://github.com/abbyssoul/libsolace/blob/master/include/solace/dictionary.hpp#L179

    I do agree that the problem is challenging and the abundance of discussions is a prof. Not convinced that “not moving” is a winning strategy. References do exist in C++, they are messy and break rules, but ultimately useful.
    So maybe we do need to figure out Optinal as to me it seems like a required foundation for a good API.

  11. About “References were introduced primarily to support operator overloading”:

    what if the language specified that, if an operator had an operand of user-defined type, then the address of the operand would be taken; and that if a user-defined operator returned a pointer, it would be automatically dereferenced?

    E.g.:

      a + b
    

    would become (in case of a global operator +() unlikely returning a pointer)

      *operator +( &a, &b )
    

    and

      ( a + b ) * c
    

    would become

      *operator( &*operator +( &a, &b ), & c ) // optionally, the language could specify that "&*" would elide
    
  12. Sorry, the last line in my previous comment should read:

    *operator *( &*operator +( &a, &b ), &c ) // optionally, the language could specify that "&*" would elide
    

    (I wrote just “operator” instead of “operator *”.)

  13. My primary use case for optional references actually would be in passing parameters:

    
    void foo(std::optional<LargeType&> param);
    
    void bar() {
        // I want to be able to optionally pass temporaries of large types without incurring a copy
        foo(LargeType());
        foo(std::nullopt);
    }
    
    

    This works for non-optional, are you aware of any proposals that would make this possible? I just use pointers for now but I do like using temporaries as much as possible, so I don’t have to give names to every intermediate computation.

  14. @John ref 2. : I tend to use: auto [ value_i_need, _ ] = …; // like the horse with no name.

  15. The post is nice. The use of non-owning pointers is since c++11 frowned upon by the majority of c++-users, thid article restores that imbalance to some extent.

  16. The article mentions that references should not be used as class data members and the reason give is “See previous”, but I am struggling to understand what the previous explanation has to do with this.
    Can someone help me understand why using references as class data members is bad?

  17. I find that advice confusing since member references are needed for dependency injection.

Comments are closed.