12
\$\begingroup\$

I omitted all free operators but the equality comparisons ones because of verbosity. I am glad about any comments and improvements.

Motivation

I know that optional references are equivalent to pointers and their implementation is just such a wrapper. But I believe they make sense in some circumstances and please correct me if I am wrong.

While implementing and using my interval map I needed to return an optional reference in one of its accessor methods, namely operator[](Key). The idea is to get a reference to a stored value or nothing. Thus I did it there with an std::optional<std::reference_wrapper<const T>>. Following this pattern of optional refs led to complications when I tried accessing an std::optional<std::reference_wrapper<T>> at some other point of time. I couldn't write something like this

int a = 0;
std::optional<std::reference_wrapper<int>> opt{a};
*opt = 42;
assert(a == 42);

but instead one has to explicitly unwrap the reference_wrapper like this

opt.value().get() = 42;
assert(a == 42);

or

opt->get() = 42
assert(a == 42);

And my motivation for optional_ref was born.

Source code

Here is the complete source code on wandbox.

optional_ref.hpp

#include <type_traits>
#include <cassert>
#include <stdexcept>
#include "range/v3/utility/concepts.hpp"
namespace fub
{
struct bad_optional_access : std::runtime_error {
 using runtime_error::runtime_error;
};
template <typename T>
 class optional_ref {
 public:
 using element_type = T;
 using reference = element_type&;
 using pointer = element_type*;
 // CONSTRUCTORS
 optional_ref() = default;
 template <typename S, 
 CONCEPT_REQUIRES_(ranges::ConvertibleTo<S*, T*>())>
 constexpr optional_ref(const optional_ref<S>& other)
 noexcept
 : m_pointer{other.get_pointer()} {}
 constexpr optional_ref(reference element)
 noexcept
 : m_pointer{std::addressof(element)} {}
 // ASSIGNMENT
 template <typename S, 
 CONCEPT_REQUIRES_(ranges::ConvertibleTo<S*, T*>())>
 constexpr optional_ref&
 operator=(const optional_ref<S>& other)
 noexcept
 {
 m_pointer = other.get_pointer();
 }
 // DESTRUCTOR
 ~optional_ref() = default;
 // SWAP
 void swap(optional_ref& other)
 noexcept
 {
 swap(m_pointer, other.m_pointer);
 }
 // OBSERVERS
 constexpr bool
 has_value() const
 noexcept
 { return m_pointer != nullptr; }
 constexpr pointer
 get_pointer() const
 noexcept
 { return m_pointer; }
 constexpr reference
 value() const
 {
 if (!has_value()) {
 throw bad_optional_access(
 "fub::optional_ref::value: optional_ref is empty."
 );
 }
 return *get_pointer();
 }
 template <typename U,
 CONCEPT_REQUIRES_(ranges::ConvertibleTo<U, reference>())>
 constexpr reference
 value_or(U&& alternative) const
 noexcept
 {
 if (has_value()) {
 return *get_pointer();
 }
 return alternative;
 }
 constexpr pointer
 operator->() const
 noexcept
 {
 assert(has_value());
 return get_pointer();
 }
 constexpr reference
 operator*() const
 noexcept
 {
 assert(has_value());
 return *get_pointer();
 }
 constexpr operator
 bool() const
 noexcept
 { return has_value(); }
 // MODIFIERS
 void reset()
 noexcept
 {
 m_pointer = nullptr;
 }
 private:
 pointer m_pointer{nullptr};
 };
// EQUALITY COMPARISON
template <typename T, typename S,
 CONCEPT_REQUIRES_(ranges::EqualityComparable<T, S>())>
 bool operator==(optional_ref<T> left, optional_ref<S> right)
 noexcept(noexcept(*left == *right))
 {
 return (!left && !right) || (left && right && *left == *right);
 }
template <typename T, typename S,
 CONCEPT_REQUIRES_(ranges::EqualityComparable<T, S>())>
 bool operator!=(optional_ref<T> left, optional_ref<S> right)
 noexcept(noexcept(left == right))
 {
 return !(left == right);
 }
// EQUALITY COMPARISON WITH T
template <typename T, typename S,
 CONCEPT_REQUIRES_(ranges::EqualityComparable<T, S>())>
 bool operator==(optional_ref<T> left, const S& right)
 noexcept(noexcept(*left == right))
 {
 return left && *left == right;
 }
template <typename T, typename S,
 CONCEPT_REQUIRES_(ranges::EqualityComparable<T, S>())>
 bool operator==(const T& left, optional_ref<S> right)
 noexcept(noexcept(left == *right))
 {
 return right && left == *right;
 }
template <typename T, typename S,
 CONCEPT_REQUIRES_(ranges::EqualityComparable<T, S>())>
 bool operator!=(optional_ref<T> left, const S& right)
 noexcept(noexcept(left == right))
 {
 return !(left == right);
 }
template <typename T, typename S,
 CONCEPT_REQUIRES_(ranges::EqualityComparable<T, S>())>
 bool operator!=(const T& left, optional_ref<S> right)
 noexcept(noexcept(left == right))
 {
 return !(left == right);
 }

Some tests (see also on wandbox)

TEST_CASE("optional refs are regular types")
{
 REQUIRE(ranges::Regular<optional_ref<int>>());
 REQUIRE(ranges::Regular<optional_ref<std::unique_ptr<int>>>());
 REQUIRE(ranges::Regular<optional_ref<char[30]>>());
 REQUIRE(ranges::Regular<optional_ref<double[]>>());
}
TEST_CASE("compare const int refs with int refs")
{
 int a = 42;
 int b = 24;
 const int c = 42;
 optional_ref<int> ref_a = a;
 optional_ref<const int> ref_x;
 REQUIRE(!ref_x);
 REQUIRE(!ref_x.has_value());
 REQUIRE(ref_a != ref_x);
 ref_x = c;
 REQUIRE(ref_a == ref_x);
 REQUIRE(ref_a.get_pointer() != ref_x.get_pointer());
 ref_x = b;
 REQUIRE(ref_a != ref_x);
 ref_x = a;
 REQUIRE(ref_a.get_pointer() == ref_x.get_pointer());
}
TEST_CASE("Arrow operator works with objects")
{
 struct A {
 int foo;
 char bar;
 };
 A a{4, '2'};
 optional_ref<A> ref{a};
 REQUIRE(ref->foo == 4);
 REQUIRE(ref->bar == '2');
}
void access_throws(optional_ref<const int> ref)
{
 REQUIRE_THROWS_AS(ref.value(), bad_optional_access);
}
TEST_CASE("throw error on using empty optional_ref")
{
 auto ref = optional_ref<int>{};
 access_throws(ref);
}
TEST_CASE("assign reference")
{
 int a = 0;
 optional_ref<int> ref{a};
 REQUIRE(ref == 0);
 *ref = 42;
 REQUIRE(ref == 42);
}
asked Apr 4, 2017 at 19:22
\$\endgroup\$
1
  • \$\begingroup\$ std::optional is a C++17 thing. Are you implementing optional_ref under C++14? \$\endgroup\$ Commented Jun 13, 2019 at 9:41

1 Answer 1

2
\$\begingroup\$

Your code seems really nice to me. It is well structured, and the use of ranges and concepts increase its diagnosing ability.

As I mentioned in my comment, std::optional is really a C++17 thing. You tagged your question , so I will assume you want implement it under C++14.

Here's some suggestions on the code.

  1. Your code is missing a trailing } to end the namespace fub.

  2. Consider wrapping your utility into a header. Add include guards.

  3. The conversion operator to bool should be marked explicit. This still allows it to be contextually converted to bool, but not otherwise implicitly converted in undesired situations.

answered Jun 13, 2019 at 9:59
\$\endgroup\$
2
  • 1
    \$\begingroup\$ "why not simply use a pointer?" Because it may be seen by many readers as not clearly enough conveying its (lack of) ownership semantics, may inadvertently have pointer arithmetic applied on it, can't be overloaded separately from other pointers and might be accidentally created by conversion from other pointers or nullptr or worse even 0, etc. An optional<reference_wrapper>, as I prefer to use, clearly states 'this is a non-owning observer, which might not exist'. \$\endgroup\$ Commented Jun 15, 2019 at 14:12
  • \$\begingroup\$ @underscore_d Agreed. Updated. \$\endgroup\$ Commented Jun 15, 2019 at 14:29

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.