azure-core
nullable.hpp
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // SPDX-License-Identifier: MIT
3 
9 #pragma once
10 
12 
13 #include <new> // for placement new
14 #include <type_traits>
15 #include <utility> // for swap and move
16 
17 namespace Azure {
18 namespace _detail {
19  struct NontrivialEmptyType final
20  {
21  constexpr NontrivialEmptyType() noexcept {}
22  };
23 } // namespace _detail
24 
30 template <class T> class Nullable final {
31  union
32  {
33  _detail::NontrivialEmptyType m_disengaged; // due to constexpr rules for the default constructor
34  T m_value;
35  };
36 
37  bool m_hasValue;
38 
39 public:
44  constexpr Nullable() : m_disengaged{}, m_hasValue(false) {}
45 
51  constexpr Nullable(T initialValue) noexcept(std::is_nothrow_move_constructible<T>::value)
52  : m_value(std::move(initialValue)), m_hasValue(true)
53  {
54  }
55 
61  Nullable(const Nullable& other) noexcept(std::is_nothrow_copy_constructible<T>::value)
62  : m_disengaged{}, m_hasValue(other.m_hasValue)
63  {
64  if (m_hasValue)
65  {
66  ::new (static_cast<void*>(&m_value)) T(other.m_value);
67  }
68  }
69 
75  Nullable(Nullable&& other) noexcept(std::is_nothrow_move_constructible<T>::value)
76  : m_disengaged{}, m_hasValue(other.m_hasValue)
77  {
78  if (m_hasValue)
79  {
80  ::new (static_cast<void*>(&m_value)) T(std::move(other.m_value));
81  }
82  }
83 
90  {
91  if (m_hasValue)
92  {
93  m_value.~T();
94  }
95  }
96 
101  void Reset() noexcept /* enforces termination */
102  {
103  if (m_hasValue)
104  {
105  m_value.~T();
106  m_hasValue = false;
107  }
108  }
109 
115  // this assumes that swap can't throw if T is nothrow move constructible because
116  // is_nothrow_swappable is added in C++17
117  void Swap(Nullable& other) noexcept(std::is_nothrow_move_constructible<T>::value)
118  {
119  if (m_hasValue)
120  {
121  if (other.m_hasValue)
122  {
123  using std::swap;
124  swap(m_value, other.m_value);
125  }
126  else
127  {
128  ::new (static_cast<void*>(&other.m_value)) T(std::move(m_value)); // throws
129  other.m_hasValue = true;
130  Reset();
131  }
132  }
133  else if (other.m_hasValue)
134  {
135  ::new (static_cast<void*>(&m_value)) T(std::move(other.m_value)); // throws
136  m_hasValue = true;
137  other.Reset();
138  }
139  }
140 
147  friend void swap(Nullable& lhs, Nullable& rhs) noexcept(
148  std::is_nothrow_move_constructible<T>::value)
149  {
150  lhs.Swap(rhs);
151  }
152 
154  Nullable& operator=(const Nullable& other)
155  {
156  // this copy and swap may be inefficient for some Ts but
157  // it's a lot less code than the standard implementation :)
158  Nullable{other}.Swap(*this);
159  return *this;
160  }
161 
163  Nullable& operator=(Nullable&& other) noexcept(std::is_nothrow_move_constructible<T>::value)
164  {
165  // this move and swap may be inefficient for some Ts but
166  // it's a lot less code than the standard implementation :)
167  Nullable{std::move(other)}.Swap(*this);
168  return *this;
169  }
170 
178  template <
179  class U = T,
180  typename std::enable_if<
181  !std::is_same<
182  Nullable,
183  typename std::remove_cv<typename std::remove_reference<U>::type>::type>::
184  value // Avoid repeated assignment
185  && !(
186  std::is_scalar<U>::value
187  && std::is_same<T, typename std::decay<U>::type>::value) // Avoid repeated
188  // assignment of
189  // equivalent scalar
190  // types
191  && std::is_constructible<T, U>::value // Ensure the type is constructible
192  && std::is_assignable<T&, U>::value, // Ensure the type is assignable
193  int>::type
194  = 0>
195  Nullable& operator=(U&& other) noexcept(
196  std::is_nothrow_constructible<T, U>::value&& std::is_nothrow_assignable<T&, U>::value)
197  {
198  if (m_hasValue)
199  {
200  m_value = std::forward<U>(other);
201  }
202  else
203  {
204  ::new (static_cast<void*>(&m_value)) T(std::forward<U>(other));
205  m_hasValue = true;
206  }
207  return *this;
208  }
209 
216  template <class... U>
217  T& Emplace(U&&... Args) noexcept(std::is_nothrow_constructible<T, U...>::value)
218  {
219  Reset();
220  ::new (static_cast<void*>(&m_value)) T(std::forward<U>(Args)...);
221  return m_value;
222  }
223 
229  bool HasValue() const noexcept { return m_hasValue; }
230 
235  const T& Value() const& noexcept
236  {
237  AZURE_ASSERT_MSG(m_hasValue, "Empty Nullable, check HasValue() first.");
238 
239  return m_value;
240  }
241 
246  T& Value() & noexcept
247  {
248  AZURE_ASSERT_MSG(m_hasValue, "Empty Nullable, check HasValue() first.");
249 
250  return m_value;
251  }
252 
257  T&& Value() && noexcept
258  {
259  AZURE_ASSERT_MSG(m_hasValue, "Empty Nullable, check HasValue() first.");
260 
261  return std::move(m_value);
262  }
263 
264  // observers
265 
270  constexpr explicit operator bool() const noexcept { return HasValue(); }
271 
280  constexpr const T* operator->() const { return std::addressof(m_value); }
281 
290  constexpr T* operator->() { return std::addressof(m_value); }
291 
300  constexpr const T& operator*() const& { return m_value; }
301 
310  constexpr T& operator*() & { return m_value; }
311 
320  constexpr T&& operator*() && { return std::move(m_value); }
321 
330  constexpr const T&& operator*() const&& { return std::move(m_value); }
331 
337  template <
338  class U = T,
339  typename std::enable_if<
340  std::is_convertible<const T&, typename std::remove_cv<T>::type>::value
341  && std::is_convertible<U, T>::value,
342  int>::type
343  = 0>
344  constexpr typename std::remove_cv<T>::type ValueOr(U&& other) const&
345  {
346  if (m_hasValue)
347  {
348  return m_value;
349  }
350 
351  return static_cast<typename std::remove_cv<T>::type>(std::forward<U>(other));
352  }
353 
359  template <
360  class U = T,
361  typename std::enable_if<
362  std::is_convertible<T, typename std::remove_cv<T>::type>::value
363  && std::is_convertible<U, T>::value,
364  int>::type
365  = 0>
366  constexpr typename std::remove_cv<T>::type ValueOr(U&& other) &&
367  {
368  if (m_hasValue)
369  {
370  return std::move(m_value);
371  }
372 
373  return static_cast<typename std::remove_cv<T>::type>(std::forward<U>(other));
374  }
375 };
376 } // namespace Azure
Azure::Nullable::HasValue
bool HasValue() const noexcept
Check whether a value is contained.
Definition: nullable.hpp:229
Azure::Nullable::Value
const T & Value() const &noexcept
Get the contained value.
Definition: nullable.hpp:235
Azure::Nullable::Reset
void Reset() noexcept
Destructs the contained value, if there is one.
Definition: nullable.hpp:101
Azure::Nullable::~Nullable
~Nullable()
Destructs the Nullable, calling the destructor for the contained value if there is one.
Definition: nullable.hpp:89
Azure::Nullable::Swap
void Swap(Nullable &other) noexcept(std::is_nothrow_move_constructible< T >::value)
Exchanges the contents.
Definition: nullable.hpp:117
Azure::Nullable::operator*
constexpr T & operator*() &
Accesses the contained value.
Definition: nullable.hpp:310
Azure::Nullable::swap
friend void swap(Nullable &lhs, Nullable &rhs) noexcept(std::is_nothrow_move_constructible< T >::value)
Invokes Azure::Nullable::Swap while having a lowercase name that satisfies swappable requirements (se...
Definition: nullable.hpp:147
Azure::Nullable::operator->
constexpr T * operator->()
Accesses the contained value.
Definition: nullable.hpp:290
Azure::Nullable::Nullable
constexpr Nullable()
Constructs a Nullable that represents the absence of value.
Definition: nullable.hpp:44
Azure::Nullable::operator->
constexpr const T * operator->() const
Accesses the contained value.
Definition: nullable.hpp:280
Azure::Nullable
Manages an optional contained value, i.e. a value that may or may not be present.
Definition: nullable.hpp:30
Azure::Nullable::Value
T & Value() &noexcept
Get the contained value reference.
Definition: nullable.hpp:246
Azure::Nullable::Nullable
Nullable(const Nullable &other) noexcept(std::is_nothrow_copy_constructible< T >::value)
Constructs a Nullable by copying another Nullable.
Definition: nullable.hpp:61
Azure
Azure SDK abstractions.
Definition: azure_assert.hpp:55
Azure::Nullable::operator=
Nullable & operator=(const Nullable &other)
Assignment operator.
Definition: nullable.hpp:154
Azure::Nullable::operator*
constexpr const T && operator*() const &&
Accesses the contained value.
Definition: nullable.hpp:330
Azure::Nullable::Emplace
T & Emplace(U &&... Args) noexcept(std::is_nothrow_constructible< T, U... >::value)
Construct the contained value in-place.
Definition: nullable.hpp:217
Azure::Nullable::operator*
constexpr const T & operator*() const &
Accesses the contained value.
Definition: nullable.hpp:300
azure_assert.hpp
Provide assert macros to use with pre-conditions.
Azure::Nullable::operator=
Nullable & operator=(Nullable &&other) noexcept(std::is_nothrow_move_constructible< T >::value)
Assignment operator with move semantics.
Definition: nullable.hpp:163
Azure::Nullable::Value
T && Value() &&noexcept
Get the contained value (as rvalue reference).
Definition: nullable.hpp:257
Azure::Nullable::ValueOr
constexpr std::remove_cv< T >::type ValueOr(U &&other) &&
Get the contained value, returns other if value is absent.
Definition: nullable.hpp:366
Azure::Nullable::ValueOr
constexpr std::remove_cv< T >::type ValueOr(U &&other) const &
Get the contained value, returns other if value is absent.
Definition: nullable.hpp:344
Azure::Nullable::operator*
constexpr T && operator*() &&
Accesses the contained value.
Definition: nullable.hpp:320
Azure::Nullable::Nullable
Nullable(Nullable &&other) noexcept(std::is_nothrow_move_constructible< T >::value)
Constructs a Nullable by moving in another Nullable.
Definition: nullable.hpp:75
Azure::Nullable::Nullable
constexpr Nullable(T initialValue) noexcept(std::is_nothrow_move_constructible< T >::value)
Constructs a Nullable having an initialValue.
Definition: nullable.hpp:51