xlnt - community edition
optional.hpp
1 // Copyright (c) 2016-2022 Thomas Fussell
2 // Copyright (c) 2024-2025 xlnt-community
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE
21 //
22 // @license: http://www.opensource.org/licenses/mit-license.php
23 // @author: see AUTHORS file
24 
25 #pragma once
26 
27 #include "xlnt/utils/exceptions.hpp"
28 #include "xlnt/utils/numeric.hpp"
29 #include <type_traits>
30 #include <cstring>
31 
32 namespace xlnt {
33 
39 template <typename T>
40 class optional
41 {
42 #if ((defined(_MSC_VER) && _MSC_VER <= 1900) || (defined(__GNUC__) && __GNUC__ < 5))
43 // Disable enhanced type checking on Visual Studio <= 2015 and GCC <5
44 #define XLNT_NOEXCEPT_VALUE_COMPAT(...) (false)
45 #else
46 #define XLNT_NOEXCEPT_VALUE_COMPAT(...) (__VA_ARGS__)
47  using ctor_copy_T_noexcept = typename std::conditional<std::is_nothrow_copy_constructible<T>{}, std::true_type, std::false_type>::type;
48  using ctor_move_T_noexcept = typename std::conditional<std::is_nothrow_move_constructible<T>{}, std::true_type, std::false_type>::type;
49  using copy_ctor_noexcept = ctor_copy_T_noexcept;
50  using move_ctor_noexcept = ctor_move_T_noexcept;
51  using set_copy_noexcept_t = typename std::conditional<std::is_nothrow_copy_constructible<T>{} && std::is_nothrow_assignable<T, T>{}, std::true_type, std::false_type>::type;
52  using set_move_noexcept_t = typename std::conditional<std::is_nothrow_move_constructible<T>{} && std::is_nothrow_move_assignable<T>{}, std::true_type, std::false_type>::type;
53  using clear_noexcept_t = typename std::conditional<std::is_nothrow_destructible<T>{}, std::true_type, std::false_type>::type;
54 #endif
55  template <typename U = T, typename std::enable_if<!std::is_floating_point<U>::value>::type * = nullptr>
59  constexpr bool compare_equal(const U &lhs, const U &rhs) const
60  {
61  return lhs == rhs;
62  }
63 
67  template <typename U = T, typename std::enable_if<std::is_floating_point<U>::value>::type * = nullptr>
68  constexpr bool compare_equal(const U &lhs, const U &rhs) const
69  {
70  return detail::float_equals(lhs, rhs);
71  }
72 
73 public:
77  optional() noexcept
78  : has_value_(false),
79  storage_{}
80  {
81  }
82 
87  optional(const T &value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_copy_T_noexcept{}))
88  : has_value_(true)
89  {
90  new (&storage_) T(value);
91  }
92 
97  optional(T &&value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_move_T_noexcept{}))
98  : has_value_(true)
99  {
100  new (&storage_) T(std::move(value));
101  }
102 
107  optional(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(copy_ctor_noexcept{}))
108  : has_value_(other.has_value_)
109  {
110  if (has_value_)
111  {
112  new (&storage_) T(other.value_ref());
113  }
114  else
115  {
116  std::memset(storage_, 0, sizeof(T));
117  }
118  }
119 
124  optional(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(move_ctor_noexcept{}))
125  : has_value_(other.has_value_)
126  {
127  if (has_value_)
128  {
129  new (&storage_) T(std::move(other.value_ref()));
130  other.clear();
131  }
132  else
133  {
134  std::memset(storage_, 0, sizeof(T));
135  }
136  }
137 
142  optional &operator=(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{} && clear_noexcept_t{}))
143  {
144  if (other.has_value_)
145  {
146  set(other.value_ref());
147  }
148  else
149  {
150  clear();
151  }
152  return *this;
153  }
154 
159  optional &operator=(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{} && clear_noexcept_t{}))
160  {
161  if (other.has_value_)
162  {
163  set(std::move(other.value_ref()));
164  other.clear();
165  }
166  else
167  {
168  clear();
169  }
170  return *this;
171  }
172 
176  ~optional() noexcept // note:: unconditional because msvc freaks out otherwise
177  {
178  clear();
179  }
180 
185  bool is_set() const noexcept
186  {
187  return has_value_;
188  }
189 
193  void set(const T &value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{}))
194  {
195  if (has_value_)
196  {
197  value_ref() = value;
198  }
199  else
200  {
201  new (&storage_) T(value);
202  has_value_ = true;
203  }
204  }
205 
209  void set(T &&value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{}))
210  {
211  // note seperate overload for two reasons (as opposed to perfect forwarding)
212  // 1. have to deal with implicit conversions internally with perfect forwarding
213  // 2. have to deal with the noexcept specfiers for all the different variations
214  // overload is just far and away the simpler solution
215  if (has_value_)
216  {
217  value_ref() = std::move(value);
218  }
219  else
220  {
221  new (&storage_) T(std::move(value));
222  has_value_ = true;
223  }
224  }
225 
229  optional &operator=(const T &rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{}))
230  {
231  set(rhs);
232  return *this;
233  }
234 
238  optional &operator=(T &&rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{}))
239  {
240  set(std::move(rhs));
241  return *this;
242  }
243 
247  void clear() noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(clear_noexcept_t{}))
248  {
249  if (has_value_)
250  {
251  reinterpret_cast<T *>(&storage_)->~T();
252  }
253  has_value_ = false;
254  }
255 
260  T &get()
261  {
262  if (!has_value_)
263  {
264  throw invalid_attribute();
265  }
266 
267  return value_ref();
268  }
269 
274  const T &get() const
275  {
276  if (!has_value_)
277  {
278  throw invalid_attribute();
279  }
280 
281  return value_ref();
282  }
283 
289  bool operator==(const optional<T> &other) const noexcept
290  {
291  if (has_value_ != other.has_value_)
292  {
293  return false;
294  }
295  if (!has_value_)
296  {
297  return true;
298  }
299  // equality is overloaded to provide fuzzy equality when T is a fp number
300  return compare_equal(value_ref(), other.value_ref());
301  }
302 
308  bool operator!=(const optional<T> &other) const noexcept
309  {
310  return !operator==(other);
311  }
312 
313 private:
314  // helpers for getting a T out of storage
315  T &value_ref() noexcept
316  {
317  return *reinterpret_cast<T *>(&storage_);
318  }
319 
320  const T &value_ref() const noexcept
321  {
322  return *reinterpret_cast<const T *>(&storage_);
323  }
324 
325  bool has_value_;
326  alignas(T) unsigned char storage_[sizeof(T)];
327 };
328 
329 #ifdef XLNT_NOEXCEPT_VALUE_COMPAT
330 #undef XLNT_NOEXCEPT_VALUE_COMPAT
331 #endif
332 
333 } // namespace xlnt
optional() noexcept
Default contructor. is_set() will be false initially.
Definition: optional.hpp:77
void clear() noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(clear_noexcept_t{}))
After this is called, is_set() will return false until a new value is provided.
Definition: optional.hpp:247
bool is_set() const noexcept
Returns true if this object currently has a value set. This should be called before accessing the val...
Definition: optional.hpp:185
optional & operator=(T &&rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{}))
Assignment operator overload. Equivalent to setting the value using optional::set.
Definition: optional.hpp:238
optional(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(move_ctor_noexcept{}))
Move constructs this optional from other. Clears the value from other if set noexcept if T move ctor ...
Definition: optional.hpp:124
bool operator==(const optional< T > &other) const noexcept
Returns true if neither this nor other have a value or both have a value and those values are equal a...
Definition: optional.hpp:289
optional(T &&value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_move_T_noexcept{}))
Constructs this optional with a value. noexcept if T move ctor is noexcept
Definition: optional.hpp:97
Enumerates the possible types a cell can be determined by it&#39;s current value.
Definition: cell.hpp:36
Exception when setting a class&#39;s attribute to an invalid value
Definition: exceptions.hpp:239
Many settings in xlnt are allowed to not have a value set. This class encapsulates a value which may ...
Definition: format.hpp:43
optional & operator=(const T &rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{}))
Assignment operator overload. Equivalent to setting the value using optional::set.
Definition: optional.hpp:229
~optional() noexcept
Destructor cleans up the T instance if set
Definition: optional.hpp:176
optional & operator=(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{} &&clear_noexcept_t{}))
Copy assignment of this optional from other noexcept if set and clear are noexcept for T& ...
Definition: optional.hpp:142
optional(const T &value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_copy_T_noexcept{}))
Constructs this optional with a value. noexcept if T copy ctor is noexcept
Definition: optional.hpp:87
optional(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(copy_ctor_noexcept{}))
Copy constructs this optional from other noexcept if T copy ctor is noexcept
Definition: optional.hpp:107
optional & operator=(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{} &&clear_noexcept_t{}))
Move assignment of this optional from other noexcept if set and clear are noexcept for T&& ...
Definition: optional.hpp:159
bool operator!=(const optional< T > &other) const noexcept
Returns false if neither this nor other have a value or both have a value and those values are equal ...
Definition: optional.hpp:308