3
\$\begingroup\$

I created a simple custom refernce_wrapper class template, which, in contrast to std::reference_wrapper, has some additional functionality:

  1. It recognizes an empty state (which allows deferring binding).
  2. (削除) It provides a rebinding option. (削除ここまで) (As noted in comments, this is actually possible with the copy assignment operator of std::reference_wrapper.)
  3. It defines operator->() for direct access to members of the referenced object.

Here is a simple code that illustrates this functionality:

Derived d;
reference_wrapper<Base> rw; // impossible with std::reference_wrapper
rw = d; // deferred binding
rw->memfn(); // 'rw.get().memfn()' with std::reference_wrapper

My implementation is as follows:

template <typename T>
class reference_wrapper
{
 T* ptr_;
public:
 reference_wrapper() : ptr_(nullptr) { }
 reference_wrapper(const reference_wrapper&) = default;
 template <typename U>
 reference_wrapper(U& obj) : ptr_(&obj) { }
 reference_wrapper& operator=(const reference_wrapper&) = default;
 template <typename U>
 reference_wrapper& operator=(U& obj)
 {
 ptr_ = &obj;
 return *this;
 }
 bool empty() const { return ptr_ == nullptr; }
 T& get() const
 {
 assert(!empty());
 return *ptr_;
 }
 operator T&() const { return get(); }
 T* operator->() const 
 {
 assert(!empty());
 return ptr_;
 }
 void clear() { ptr_ = nullptr; }
};
template <typename T>
inline auto ref(T& r)
{
 return reference_wrapper<T>(r);
}
template <typename T>
inline auto cref(const T& r)
{
 return reference_wrapper<const T>(r);
}

Complete live demo link: https://godbolt.org/z/MGYnWKo7o

Notes:

  1. I don't care about binding rvalues.
  2. For performance reasons, I want to avoid unnecessary branching in production builds. This is why the access to the referenced value is protected with assertions.

Rationale:

Why am I not using a pointer instead?

  1. I don't like raw pointers to be used in high-level application code.
  2. I want the access to the referenced object to be protected with assertions.

I will appreciate any comments/suggestions for improvement if anyone finds anything wrong with this code.

asked Aug 27 at 15:03
\$\endgroup\$
5
  • 2
    \$\begingroup\$ Pretty sure you can rebind std::reference_wrapper. \$\endgroup\$ Commented Aug 27 at 20:25
  • \$\begingroup\$ I should also point out: godbolt.org/z/s4bqG137Y \$\endgroup\$ Commented Aug 27 at 22:07
  • \$\begingroup\$ @indi You're right, the rebinding is possible, just not directly, but via the copy assignment operator. As for std::optional<std::reference_wrapper<T>>, I don't like this option at all. First, it is memory-inefficient (the internal pointer can recognize the empty state by itself, while optional needs an additional flag). Second, the access to the members of the referenced object requires rw->get().f() instead of simple rw->f(). \$\endgroup\$ Commented Aug 28 at 8:16
  • 1
    \$\begingroup\$ I wouldn’t recommend using std::optional<std::reference_wrapper<T>> either; I’d wait for C++26 and just use std::optional<T&> (or use boost::optional<T&>, or some such, then switch to std::optional<T&> in the future). But the point is that whatever you make should look like an optional reference... because that’s literally what you’re describing. It should not be a "potentially broken reference that sometimes looks like a pointer". You should make an optional_reference<T> that behaves like a optional<T&>, with your optimizations. \$\endgroup\$ Commented Aug 28 at 9:51
  • \$\begingroup\$ @indi I like the name optional_reference, it sounds good :) \$\endgroup\$ Commented Aug 28 at 11:12

1 Answer 1

5
\$\begingroup\$

Correctness

Your assignment and construction templates are overly broad. You need sfinae or a requires clause to block reference_wrapper<T>, even if you don't care about dangling rvalues. Which you should.

Naming

What you have is a checked pointer. It is very surprising to have a type with reference in its name be nullable, especially because it shares a name with something in std.

clear could be named reset, to match smart pointers.

Toby Speight
87.8k14 gold badges104 silver badges325 bronze badges
answered Aug 27 at 17:51
\$\endgroup\$
1
  • \$\begingroup\$ I agree with all of that. I realized that clear does not make much sense here, and reset also matches std::optional. As for naming, it should be different. I just don't want it to contain "pointer", since it is actually somewhere in between both. Aa s pointer, it has an empty state, and operator->(). However, it also provides a cast to T&, which is not a pointer-thing. Also, it is initialized/assigned to by objects, not pointer arguments. \$\endgroup\$ Commented Aug 28 at 8:23

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.