76

C++ Core Guidelines has been presented recently (congrats!) and I am concerned about gsl::not_null type. As stated in I.12: Declare a pointer that must not be null as not_null:

To help avoid dereferencing nullptr errors. To improve performance by avoiding redundant checks for nullptr.

...

By stating the intent in source, implementers and tools can provide better diagnostics, such as finding some classes of errors through static analysis, and perform optimizations, such as removing branches and null tests.

The intent is clear. However, we already have a language feature for that. Pointers that cannot be null are called references. And while references cannot be rebound once they are created, this problem is solved by std::reference_wrapper.

The main difference between gsl::not_null and std::reference_wrapper I see in that the latter can be used only instead of pointers, while the former works on anything nullptr-assignable (quote from F.17: Use a not_null to indicate that "null" is not a valid value):

not_null is not just for built-in pointers. It works for array_view, string_view, unique_ptr, shared_ptr, and other pointer-like types.

I imagine the feature comparison table like the following:

T&:

  • Cannot store nullptr? - Yes
  • Rebindable? - No
  • Can be used instead of something other than pointers? - No

std::reference_wrapper<T>:

  • Cannot store nullptr? - Yes
  • Rebindable? - Yes
  • Can be used instead of something other than pointers? - No

gsl::not_null<T*>:

  • Cannot store nullptr? - Yes
  • Rebindable? - Yes
  • Can be used instead of something other than pointers? - Yes

Now here are the questions, finally:

  1. Is my understanding of differences between these concepts correct?
  2. Does that mean that std::reference_wrapper is now useless?

PS I created tags cpp-core-guidelines and guideline-support-library for this, I hope properly.

Mikhail
  • 20,685
  • 7
  • 70
  • 146
  • `Can be used instead of something other that pointers?` - typo in there somewhere? – nneonneo Oct 23 '15 at 18:42
  • @nneonneo No typos. For example we can use references instead of pointers as function arguments to highlight that argument cannot be null. We can also use both `std::reference_wrapper` and `gsl::not_null` for that purpose. But we cannot use references or `std::reference_wrapper` instead of, say, `std::shared_ptr` passed to a function. – Mikhail Oct 23 '15 at 18:45
  • I'm confused about the "something other that pointers" part - it doesn't seem grammatically correct. – nneonneo Oct 23 '15 at 20:35
  • @nneonneo Feel free to edit the question to fix the grammar :) Should it be "than"? – Mikhail Oct 23 '15 at 21:14

2 Answers2

63

References are not pointers that cannot be null. References are semantically very different to pointers.

References have value assignment and comparison semantics; that is, assignment or comparison operations involving references read and write the referenced value. Pointers have (counterintuitively) reference assignment and comparison semantics; that is, assignment or comparison operations involving pointers read and write the reference itself (i.e. the address of the referenced object).

As you noted, references cannot be rebound (due to their value assignment semantics), but the reference_wrapper<T> class template can be rebound, because it has reference assignment semantics. This is because reference_wrapper<T> is designed to be used with STL containers and algorithms, and would not behave correctly if its copy assignment operator didn't do the same thing as its copy constructor. However, reference_wrapper<T> still has value comparison semantics, like a reference, so it behaves very differently to pointers when used with STL containers and algorithms. For example, set<T*> can contain pointers to different objects with the same value, while set<reference_wrapper<T>> can contain a reference to only one object with a given value.

The not_null<T*> class template has reference assignment and comparison semantics, like a pointer; it is a pointer-like type. This means that it behaves like a pointer when used with STL containers and algorithms. It just can't be null.

So, you are right in your assessment, except you forgot about comparison semantics. And no, reference_wrapper<T> will not be made obsolete by any kind of pointer-like type, because it has reference-like value comparison semantics.

Joseph Thomson
  • 9,888
  • 1
  • 34
  • 38
  • Good point on comparison semantics. "For example, set can contain references to objects with the same value, while set> cannot." - what does that mean? – Mikhail Jan 30 '17 at 15:11
  • 2
    @Mikhail I've tried to rewrite to clarify. `set` only contains one object of any given value at any one time. `T*` and `reference_wrapper` have different comparison semantics, so they behave differently when put in a `set`. – Joseph Thomson Jan 30 '17 at 16:56
  • Excellent explanation. I think there is another very important difference mentioned here https://stackoverflow.com/a/29377642/315734 regarding ownership: reference_wrapper means not owned, not_null means owned. – mentics Oct 27 '17 at 13:47
  • @taotree AFAIK, `not_null` does not currently work with smart pointers (see https://github.com/Microsoft/GSL/issues/89). – Joseph Thomson Oct 27 '17 at 14:56
  • @JosephThomson Right, but it could (based on that discussion sounds like they're just going slow to decide how they want to), and here's an implementation that does already: https://github.com/dropbox/nn – mentics Oct 27 '17 at 19:12
  • 3
    I'd point out that `reference_wrapper` itself has *no* comparison semantics (operators). That such semantics are being seen by containers must be transitive through its implicit conversion to the referenced type. A quick test shows that I can indeed make an `std::set< std::reference_wrapper >` and emplace 'references' into that and get the expected sorted result and prevention of duplicates, but the spec and implementation of the wrapper show no comparison operators, so those must be acquired via implicit conversions (in this case to the imaginary `bool operator<(int, int)`. – underscore_d Sep 26 '18 at 19:40
12

I think that there are still use-cases for std::reference_wrapper which are not covered by gsl::not_null. Basically, std::reference_wrapper mirrors a reference and has a operator T& conversion, while not_nullhas a pointer interface with operator->. One use-case that comes to my mind immediatly is when creating a thread:

void funcWithReference(int& x) { x = 42; }
int i=0;
auto t = std::thread( funcWithReference, std::ref(i) );

If I don't have control over funcWithReference, I cannot use not_null.

The same applies to functors for algorithms, and I had to use it for binding boost::signals too.

Jens
  • 9,058
  • 2
  • 26
  • 43
  • I think here you are binding `int&` argument to a temporary `std::reference_wrapper`, which will be destroyed once the thread is created, and you will have a dangling reference. – Mikhail Oct 24 '15 at 07:35
  • 5
    @Mikhail I don't think so. The thread constructor copies/moves the arguments to thread-accessible storage, and then uses thiese "copies" to call the function in the thread context. If you want to pass something by reference, you have to use `std::ref`. – Jens Oct 24 '15 at 07:40
  • 2
    Of course you have to take care that the referenced object still lives. – Jens Oct 19 '16 at 20:29