xlnt
optional.hpp
1 // Copyright (c) 2016-2022 Thomas Fussell
2 // Copyright (c) 2024 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 "xlnt/xlnt_config.hpp"
30 #include <type_traits>
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  {
80  }
81 
86  optional(const T &value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_copy_T_noexcept{}))
87  : has_value_(true)
88  {
89  new (&storage_) T(value);
90  }
91 
96  optional(T &&value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_move_T_noexcept{}))
97  : has_value_(true)
98  {
99  new (&storage_) T(std::move(value));
100  }
101 
106  optional(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(copy_ctor_noexcept{}))
107  : has_value_(other.has_value_)
108  {
109  if (has_value_)
110  {
111  new (&storage_) T(other.value_ref());
112  }
113  }
114 
119  optional(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(move_ctor_noexcept{}))
120  : has_value_(other.has_value_)
121  {
122  if (has_value_)
123  {
124  new (&storage_) T(std::move(other.value_ref()));
125  other.clear();
126  }
127  }
128 
133  optional &operator=(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{} && clear_noexcept_t{}))
134  {
135  if (other.has_value_)
136  {
137  set(other.value_ref());
138  }
139  else
140  {
141  clear();
142  }
143  return *this;
144  }
145 
150  optional &operator=(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{} && clear_noexcept_t{}))
151  {
152  if (other.has_value_)
153  {
154  set(std::move(other.value_ref()));
155  other.clear();
156  }
157  else
158  {
159  clear();
160  }
161  return *this;
162  }
163 
167  ~optional() noexcept // note:: unconditional because msvc freaks out otherwise
168  {
169  clear();
170  }
171 
176  bool is_set() const noexcept
177  {
178  return has_value_;
179  }
180 
184  void set(const T &value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{}))
185  {
186 #if defined(__GNUC__) && !defined(__clang__)
187 #pragma GCC diagnostic push
188 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
189 #endif
190  if (has_value_)
191  {
192  value_ref() = value;
193  }
194  else
195  {
196  new (&storage_) T(value);
197  has_value_ = true;
198  }
199 #if defined(__GNUC__) && !defined(__clang__)
200 #pragma GCC diagnostic pop
201 #endif
202  }
203 
207  void set(T &&value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{}))
208  {
209  // note seperate overload for two reasons (as opposed to perfect forwarding)
210  // 1. have to deal with implicit conversions internally with perfect forwarding
211  // 2. have to deal with the noexcept specfiers for all the different variations
212  // overload is just far and away the simpler solution
213 #if defined(__GNUC__) && !defined(__clang__)
214 #pragma GCC diagnostic push
215 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
216 #endif
217  if (has_value_)
218  {
219  value_ref() = std::move(value);
220  }
221  else
222  {
223  new (&storage_) T(std::move(value));
224  has_value_ = true;
225  }
226 #if defined(__GNUC__) && !defined(__clang__)
227 #pragma GCC diagnostic pop
228 #endif
229  }
230 
234  optional &operator=(const T &rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{}))
235  {
236  set(rhs);
237  return *this;
238  }
239 
243  optional &operator=(T &&rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{}))
244  {
245  set(std::move(rhs));
246  return *this;
247  }
248 
252  void clear() noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(clear_noexcept_t{}))
253  {
254  if (has_value_)
255  {
256  reinterpret_cast<T *>(&storage_)->~T();
257  }
258  has_value_ = false;
259  }
260 
265  T &get()
266  {
267  if (!has_value_)
268  {
269  throw invalid_attribute();
270  }
271 
272  return value_ref();
273  }
274 
279  const T &get() const
280  {
281  if (!has_value_)
282  {
283  throw invalid_attribute();
284  }
285 
286  return value_ref();
287  }
288 
294  bool operator==(const optional<T> &other) const noexcept
295  {
296  if (has_value_ != other.has_value_)
297  {
298  return false;
299  }
300  if (!has_value_)
301  {
302  return true;
303  }
304  // equality is overloaded to provide fuzzy equality when T is a fp number
305  return compare_equal(value_ref(), other.value_ref());
306  }
307 
313  bool operator!=(const optional<T> &other) const noexcept
314  {
315  return !operator==(other);
316  }
317 
318 private:
319  // helpers for getting a T out of storage
320  T &value_ref() noexcept
321  {
322  return *reinterpret_cast<T *>(&storage_);
323  }
324 
325  const T &value_ref() const noexcept
326  {
327  return *reinterpret_cast<const T *>(&storage_);
328  }
329 
330  bool has_value_;
331  typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
332 };
333 
334 #ifdef XLNT_NOEXCEPT_VALUE_COMPAT
335 #undef XLNT_NOEXCEPT_VALUE_COMPAT
336 #endif
337 
338 } // 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:252
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:176
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:243
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:119
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:294
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:96
Enumerates the possible types a cell can be determined by it&#39;s current value.
Definition: cell.hpp:37
Exception when setting a class&#39;s attribute to an invalid value
Definition: exceptions.hpp:240
Many settings in xlnt are allowed to not have a value set. This class encapsulates a value which may ...
Definition: format.hpp:44
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:234
~optional() noexcept
Destructor cleans up the T instance if set
Definition: optional.hpp:167
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:133
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:86
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:106
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:150
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:313