ArmNN  NotReleased
CounterDirectory.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include "CounterDirectory.hpp"
7 #include "ProfilingUtils.hpp"
8 
9 #include <armnn/Exceptions.hpp>
10 #include <armnn/Conversion.hpp>
11 
12 #include <boost/core/ignore_unused.hpp>
13 #include <boost/format.hpp>
14 
15 namespace armnn
16 {
17 
18 namespace profiling
19 {
20 
21 const Category* CounterDirectory::RegisterCategory(const std::string& categoryName,
22  const Optional<uint16_t>& deviceUid,
23  const Optional<uint16_t>& counterSetUid)
24 {
25  // Check that the given category name is valid
26  if (categoryName.empty() ||
27  !IsValidSwTraceString<SwTraceNameCharPolicy>(categoryName))
28  {
29  throw InvalidArgumentException("Trying to register a category with an invalid name");
30  }
31 
32  // Check that the given category is not already registered
33  if (IsCategoryRegistered(categoryName))
34  {
36  boost::str(boost::format("Trying to register a category already registered (\"%1%\")")
37  % categoryName));
38  }
39 
40  // Check that a device with the given (optional) UID is already registered
41  uint16_t deviceUidValue = deviceUid.has_value() ? deviceUid.value() : 0;
42  if (deviceUidValue > 0)
43  {
44  // Check that the (optional) device is already registered
45  if (!IsDeviceRegistered(deviceUidValue))
46  {
48  boost::str(boost::format("Trying to connect a category (\"%1%\") to a device that is "
49  "not registered (UID %2%)")
50  % categoryName
51  % deviceUidValue));
52  }
53  }
54 
55  // Check that a counter set with the given (optional) UID is already registered
56  uint16_t counterSetUidValue = counterSetUid.has_value() ? counterSetUid.value() : 0;
57  if (counterSetUidValue > 0)
58  {
59  // Check that the (optional) counter set is already registered
60  if (!IsCounterSetRegistered(counterSetUidValue))
61  {
63  boost::str(boost::format("Trying to connect a category (name: \"%1%\") to a counter set "
64  "that is not registered (UID: %2%)")
65  % categoryName
66  % counterSetUidValue));
67  }
68  }
69 
70  // Create the category
71  CategoryPtr category = std::make_unique<Category>(categoryName, deviceUidValue, counterSetUidValue);
72  BOOST_ASSERT(category);
73 
74  // Get the raw category pointer
75  const Category* categoryPtr = category.get();
76  BOOST_ASSERT(categoryPtr);
77 
78  // Register the category
79  m_Categories.insert(std::move(category));
80 
81  return categoryPtr;
82 }
83 
84 const Device* CounterDirectory::RegisterDevice(const std::string& deviceName,
85  uint16_t cores,
86  const Optional<std::string>& parentCategoryName)
87 {
88  // Check that the given device name is valid
89  if (deviceName.empty() ||
90  !IsValidSwTraceString<SwTraceCharPolicy>(deviceName))
91  {
92  throw InvalidArgumentException("Trying to register a device with an invalid name");
93  }
94 
95  // Check that a device with the given name is not already registered
96  if (IsDeviceRegistered(deviceName))
97  {
99  boost::str(boost::format("Trying to register a device already registered (\"%1%\")")
100  % deviceName));
101  }
102 
103  // Peek the next UID, do not get an actual valid UID just now as we don't want to waste a good UID in case
104  // the registration fails. We'll get a proper one once we're sure that the device can be registered
105  uint16_t deviceUidPeek = GetNextUid(true);
106 
107  // Check that a category with the given (optional) parent category name is already registered
108  Category* parentCategoryPtr = nullptr;
109  if (parentCategoryName.has_value())
110  {
111  // Get the (optional) parent category name
112  const std::string& parentCategoryNameValue = parentCategoryName.value();
113  if (parentCategoryNameValue.empty())
114  {
116  boost::str(boost::format("Trying to connect a device (name: \"%1%\") to an invalid "
117  "parent category (name: \"%2%\")")
118  % deviceName
119  % parentCategoryNameValue));
120  }
121 
122  // Check that the given parent category is already registered
123  auto categoryIt = FindCategory(parentCategoryNameValue);
124  if (categoryIt == m_Categories.end())
125  {
127  boost::str(boost::format("Trying to connect a device (name: \"%1%\") to a parent category that "
128  "is not registered (name: \"%2%\")")
129  % deviceName
130  % parentCategoryNameValue));
131  }
132 
133  // Get the parent category
134  const CategoryPtr& parentCategory = *categoryIt;
135  BOOST_ASSERT(parentCategory);
136 
137  // Check that the given parent category is not already connected to another device
138  if (parentCategory->m_DeviceUid != 0 && parentCategory->m_DeviceUid != deviceUidPeek)
139  {
141  boost::str(boost::format("Trying to connect a device (UID: %1%) to a parent category that is "
142  "already connected to a different device "
143  "(category \"%2%\" connected to device %3%)")
144  % deviceUidPeek
145  % parentCategoryNameValue
146  % parentCategory->m_DeviceUid));
147  }
148 
149  // The parent category can be associated to the device that is about to be registered.
150  // Get the raw pointer to the parent category (to be used later when the device is actually been
151  // registered, to make sure that the category is associated to an existing device)
152  parentCategoryPtr = parentCategory.get();
153  }
154 
155  // Get the device UID
156  uint16_t deviceUid = GetNextUid();
157  BOOST_ASSERT(deviceUid == deviceUidPeek);
158 
159  // Create the device
160  DevicePtr device = std::make_unique<Device>(deviceUid, deviceName, cores);
161  BOOST_ASSERT(device);
162 
163  // Get the raw device pointer
164  const Device* devicePtr = device.get();
165  BOOST_ASSERT(devicePtr);
166 
167  // Register the device
168  m_Devices.insert(std::make_pair(deviceUid, std::move(device)));
169 
170  // Connect the device to the parent category, if required
171  if (parentCategoryPtr)
172  {
173  // Set the device UID in the parent category
174  parentCategoryPtr->m_DeviceUid = deviceUid;
175  }
176 
177  return devicePtr;
178 }
179 
180 const CounterSet* CounterDirectory::RegisterCounterSet(const std::string& counterSetName,
181  uint16_t count,
182  const Optional<std::string>& parentCategoryName)
183 {
184  // Check that the given counter set name is valid
185  if (counterSetName.empty() ||
186  !IsValidSwTraceString<SwTraceNameCharPolicy>(counterSetName))
187  {
188  throw InvalidArgumentException("Trying to register a counter set with an invalid name");
189  }
190 
191  // Check that a counter set with the given name is not already registered
192  if (IsCounterSetRegistered(counterSetName))
193  {
195  boost::str(boost::format("Trying to register a counter set already registered (\"%1%\")")
196  % counterSetName));
197  }
198 
199  // Peek the next UID, do not get an actual valid UID just now as we don't want to waste a good UID in case
200  // the registration fails. We'll get a proper one once we're sure that the counter set can be registered
201  uint16_t counterSetUidPeek = GetNextUid(true);
202 
203  // Check that a category with the given (optional) parent category name is already registered
204  Category* parentCategoryPtr = nullptr;
205  if (parentCategoryName.has_value())
206  {
207  // Get the (optional) parent category name
208  const std::string& parentCategoryNameValue = parentCategoryName.value();
209  if (parentCategoryNameValue.empty())
210  {
212  boost::str(boost::format("Trying to connect a counter set (UID: %1%) to an invalid "
213  "parent category (name: \"%2%\")")
214  % counterSetUidPeek
215  % parentCategoryNameValue));
216  }
217 
218  // Check that the given parent category is already registered
219  auto it = FindCategory(parentCategoryNameValue);
220  if (it == m_Categories.end())
221  {
223  boost::str(boost::format("Trying to connect a counter set (UID: %1%) to a parent category "
224  "that is not registered (name: \"%2%\")")
225  % counterSetUidPeek
226  % parentCategoryNameValue));
227  }
228 
229  // Get the parent category
230  const CategoryPtr& parentCategory = *it;
231  BOOST_ASSERT(parentCategory);
232 
233  // Check that the given parent category is not already connected to another counter set
234  if (parentCategory->m_CounterSetUid != 0 && parentCategory->m_CounterSetUid != counterSetUidPeek)
235  {
237  boost::str(boost::format("Trying to connect a counter set (UID: %1%) to a parent category "
238  "that is already connected to a different counter set "
239  "(category \"%2%\" connected to counter set %3%)")
240  % counterSetUidPeek
241  % parentCategoryNameValue
242  % parentCategory->m_CounterSetUid));
243  }
244 
245  // The parent category can be associated to the counter set that is about to be registered.
246  // Get the raw pointer to the parent category (to be used later when the counter set is actually been
247  // registered, to make sure that the category is associated to an existing counter set)
248  parentCategoryPtr = parentCategory.get();
249  }
250 
251  // Get the counter set UID
252  uint16_t counterSetUid = GetNextUid();
253  BOOST_ASSERT(counterSetUid == counterSetUidPeek);
254 
255  // Create the counter set
256  CounterSetPtr counterSet = std::make_unique<CounterSet>(counterSetUid, counterSetName, count);
257  BOOST_ASSERT(counterSet);
258 
259  // Get the raw counter set pointer
260  const CounterSet* counterSetPtr = counterSet.get();
261  BOOST_ASSERT(counterSetPtr);
262 
263  // Register the counter set
264  m_CounterSets.insert(std::make_pair(counterSetUid, std::move(counterSet)));
265 
266  // Connect the counter set to the parent category, if required
267  if (parentCategoryPtr)
268  {
269  // Set the counter set UID in the parent category
270  parentCategoryPtr->m_CounterSetUid = counterSetUid;
271  }
272 
273  return counterSetPtr;
274 }
275 
277  const uint16_t uid,
278  const std::string& parentCategoryName,
279  uint16_t counterClass,
280  uint16_t interpolation,
281  double multiplier,
282  const std::string& name,
283  const std::string& description,
284  const Optional<std::string>& units,
285  const Optional<uint16_t>& numberOfCores,
286  const Optional<uint16_t>& deviceUid,
287  const Optional<uint16_t>& counterSetUid)
288 {
289  boost::ignore_unused(backendId);
290 
291  // Check that the given parent category name is valid
292  if (parentCategoryName.empty() ||
293  !IsValidSwTraceString<SwTraceNameCharPolicy>(parentCategoryName))
294  {
295  throw InvalidArgumentException("Trying to register a counter with an invalid parent category name");
296  }
297 
298  // Check that the given class is valid
299  if (counterClass != 0 && counterClass != 1)
300  {
301  throw InvalidArgumentException("Trying to register a counter with an invalid class");
302  }
303 
304  // Check that the given interpolation is valid
305  if (interpolation != 0 && interpolation != 1)
306  {
307  throw InvalidArgumentException("Trying to register a counter with an invalid interpolation");
308  }
309 
310  // Check that the given multiplier is valid
311  if (multiplier == .0f)
312  {
313  throw InvalidArgumentException("Trying to register a counter with an invalid multiplier");
314  }
315 
316  // Check that the given name is valid
317  if (name.empty() ||
318  !IsValidSwTraceString<SwTraceCharPolicy>(name))
319  {
320  throw InvalidArgumentException("Trying to register a counter with an invalid name");
321  }
322 
323  // Check that the given description is valid
324  if (description.empty() ||
325  !IsValidSwTraceString<SwTraceCharPolicy>(description))
326  {
327  throw InvalidArgumentException("Trying to register a counter with an invalid description");
328  }
329 
330  // Check that the given units are valid
331  if (units.has_value()
332  && !IsValidSwTraceString<SwTraceNameCharPolicy>(units.value()))
333  {
334  throw InvalidArgumentException("Trying to register a counter with a invalid units");
335  }
336 
337  // Check that the given parent category is registered
338  auto categoryIt = FindCategory(parentCategoryName);
339  if (categoryIt == m_Categories.end())
340  {
342  boost::str(boost::format("Trying to connect a counter to a category "
343  "that is not registered (name: \"%1%\")")
344  % parentCategoryName));
345  }
346 
347  // Get the parent category
348  const CategoryPtr& parentCategory = *categoryIt;
349  BOOST_ASSERT(parentCategory);
350 
351  // Check that a counter with the given name is not already registered within the parent category
352  const std::vector<uint16_t>& parentCategoryCounters = parentCategory->m_Counters;
353  for (uint16_t parentCategoryCounterUid : parentCategoryCounters)
354  {
355  const Counter* parentCategoryCounter = GetCounter(parentCategoryCounterUid);
356  BOOST_ASSERT(parentCategoryCounter);
357 
358  if (parentCategoryCounter->m_Name == name)
359  {
361  boost::str(boost::format("Trying to register a counter to category \"%1%\" with a name that "
362  "is already used within that category (name: \"%2%\")")
363  % parentCategoryName
364  % name));
365  }
366  }
367 
368  // Check that a counter set with the given (optional) UID is already registered
369  uint16_t counterSetUidValue = counterSetUid.has_value() ? counterSetUid.value() : 0;
370  if (counterSetUidValue > 0)
371  {
372  // Check that the (optional) counter set is already registered
373  if (!IsCounterSetRegistered(counterSetUidValue))
374  {
376  boost::str(boost::format("Trying to connect a counter to a counter set that is "
377  "not registered (counter set UID: %1%)")
378  % counterSetUidValue));
379  }
380  }
381 
382  // Get the number of cores (this call may throw)
383  uint16_t deviceUidValue = deviceUid.has_value() ? deviceUid.value() : 0;
384  uint16_t deviceCores = GetNumberOfCores(numberOfCores, deviceUidValue, parentCategory);
385 
386  // Get the counter UIDs and calculate the max counter UID
387  std::vector<uint16_t> counterUids = GetNextCounterUids(uid, deviceCores);
388  BOOST_ASSERT(!counterUids.empty());
389  uint16_t maxCounterUid = deviceCores <= 1 ? counterUids.front() : counterUids.back();
390 
391  // Get the counter units
392  const std::string unitsValue = units.has_value() ? units.value() : "";
393 
394  // Create the counter
395  CounterPtr counter = std::make_shared<Counter>(armnn::profiling::BACKEND_ID,
396  counterUids.front(),
397  maxCounterUid,
398  counterClass,
399  interpolation,
400  multiplier,
401  name,
402  description,
403  unitsValue,
404  deviceUidValue,
405  counterSetUidValue);
406  BOOST_ASSERT(counter);
407 
408  // Get the raw counter pointer
409  const Counter* counterPtr = counter.get();
410  BOOST_ASSERT(counterPtr);
411 
412  // Process multiple counters if necessary
413  for (uint16_t counterUid : counterUids)
414  {
415  // Connect the counter to the parent category
416  parentCategory->m_Counters.push_back(counterUid);
417 
418  // Register the counter
419  m_Counters.insert(std::make_pair(counterUid, counter));
420  }
421 
422  return counterPtr;
423 }
424 
425 const Category* CounterDirectory::GetCategory(const std::string& categoryName) const
426 {
427  auto it = FindCategory(categoryName);
428  if (it == m_Categories.end())
429  {
430  return nullptr;
431  }
432 
433  const Category* category = it->get();
434  BOOST_ASSERT(category);
435 
436  return category;
437 }
438 
439 const Device* CounterDirectory::GetDevice(uint16_t deviceUid) const
440 {
441  auto it = FindDevice(deviceUid);
442  if (it == m_Devices.end())
443  {
444  return nullptr;
445  }
446 
447  const Device* device = it->second.get();
448  BOOST_ASSERT(device);
449  BOOST_ASSERT(device->m_Uid == deviceUid);
450 
451  return device;
452 }
453 
454 const CounterSet* CounterDirectory::GetCounterSet(uint16_t counterSetUid) const
455 {
456  auto it = FindCounterSet(counterSetUid);
457  if (it == m_CounterSets.end())
458  {
459  return nullptr;
460  }
461 
462  const CounterSet* counterSet = it->second.get();
463  BOOST_ASSERT(counterSet);
464  BOOST_ASSERT(counterSet->m_Uid == counterSetUid);
465 
466  return counterSet;
467 }
468 
469 const Counter* CounterDirectory::GetCounter(uint16_t counterUid) const
470 {
471  auto it = FindCounter(counterUid);
472  if (it == m_Counters.end())
473  {
474  return nullptr;
475  }
476 
477  const Counter* counter = it->second.get();
478  BOOST_ASSERT(counter);
479  BOOST_ASSERT(counter->m_Uid <= counterUid);
480  BOOST_ASSERT(counter->m_Uid <= counter->m_MaxCounterUid);
481 
482  return counter;
483 }
484 
485 bool CounterDirectory::IsCategoryRegistered(const std::string& categoryName) const
486 {
487  auto it = FindCategory(categoryName);
488 
489  return it != m_Categories.end();
490 }
491 
492 bool CounterDirectory::IsDeviceRegistered(uint16_t deviceUid) const
493 {
494  auto it = FindDevice(deviceUid);
495 
496  return it != m_Devices.end();
497 }
498 
499 bool CounterDirectory::IsDeviceRegistered(const std::string& deviceName) const
500 {
501  auto it = FindDevice(deviceName);
502 
503  return it != m_Devices.end();
504 }
505 
506 bool CounterDirectory::IsCounterSetRegistered(uint16_t counterSetUid) const
507 {
508  auto it = FindCounterSet(counterSetUid);
509 
510  return it != m_CounterSets.end();
511 }
512 
513 bool CounterDirectory::IsCounterSetRegistered(const std::string& counterSetName) const
514 {
515  auto it = FindCounterSet(counterSetName);
516 
517  return it != m_CounterSets.end();
518 }
519 
520 bool CounterDirectory::IsCounterRegistered(uint16_t counterUid) const
521 {
522  auto it = FindCounter(counterUid);
523 
524  return it != m_Counters.end();
525 }
526 
527 bool CounterDirectory::IsCounterRegistered(const std::string& counterName) const
528 {
529  auto it = FindCounter(counterName);
530 
531  return it != m_Counters.end();
532 }
533 
535 {
536  // Clear all the counter directory contents
537  m_Categories.clear();
538  m_Devices.clear();
539  m_CounterSets.clear();
540  m_Counters.clear();
541 }
542 
543 CategoriesIt CounterDirectory::FindCategory(const std::string& categoryName) const
544 {
545  return std::find_if(m_Categories.begin(), m_Categories.end(), [&categoryName](const CategoryPtr& category)
546  {
547  BOOST_ASSERT(category);
548 
549  return category->m_Name == categoryName;
550  });
551 }
552 
553 DevicesIt CounterDirectory::FindDevice(uint16_t deviceUid) const
554 {
555  return m_Devices.find(deviceUid);
556 }
557 
558 DevicesIt CounterDirectory::FindDevice(const std::string& deviceName) const
559 {
560  return std::find_if(m_Devices.begin(), m_Devices.end(), [&deviceName](const auto& pair)
561  {
562  BOOST_ASSERT(pair.second);
563  BOOST_ASSERT(pair.second->m_Uid == pair.first);
564 
565  return pair.second->m_Name == deviceName;
566  });
567 }
568 
569 CounterSetsIt CounterDirectory::FindCounterSet(uint16_t counterSetUid) const
570 {
571  return m_CounterSets.find(counterSetUid);
572 }
573 
574 CounterSetsIt CounterDirectory::FindCounterSet(const std::string& counterSetName) const
575 {
576  return std::find_if(m_CounterSets.begin(), m_CounterSets.end(), [&counterSetName](const auto& pair)
577  {
578  BOOST_ASSERT(pair.second);
579  BOOST_ASSERT(pair.second->m_Uid == pair.first);
580 
581  return pair.second->m_Name == counterSetName;
582  });
583 }
584 
585 CountersIt CounterDirectory::FindCounter(uint16_t counterUid) const
586 {
587  return m_Counters.find(counterUid);
588 }
589 
590 CountersIt CounterDirectory::FindCounter(const std::string& counterName) const
591 {
592  return std::find_if(m_Counters.begin(), m_Counters.end(), [&counterName](const auto& pair)
593  {
594  BOOST_ASSERT(pair.second);
595  BOOST_ASSERT(pair.second->m_Uid == pair.first);
596 
597  return pair.second->m_Name == counterName;
598  });
599 }
600 
601 uint16_t CounterDirectory::GetNumberOfCores(const Optional<uint16_t>& numberOfCores,
602  uint16_t deviceUid,
603  const CategoryPtr& parentCategory)
604 {
605  BOOST_ASSERT(parentCategory);
606 
607  // To get the number of cores, apply the following rules:
608  //
609  // 1. If numberOfCores is set then take it as the deviceCores value
610  // 2. If numberOfCores is not set then check to see if this counter is directly associated with a device,
611  // if so then that devices number of cores is taken as the deviceCores value
612  // 3. If neither of the above is set then look at the category to see if it has a device associated with it,
613  // if it does then take that device's numberOfCores as the deviceCores value
614  // 4. If none of the above holds then set deviceCores to zero
615 
616  // 1. If numberOfCores is set then take it as the deviceCores value
617  if (numberOfCores.has_value())
618  {
619  // Get the number of cores
620  return numberOfCores.value();
621  }
622 
623  // 2. If numberOfCores is not set then check to see if this counter is directly associated with a device,
624  // if so then that devices number of cores is taken as the deviceCores value
625  if (deviceUid > 0)
626  {
627  // Check that the (optional) device is already registered
628  auto deviceIt = FindDevice(deviceUid);
629  if (deviceIt == m_Devices.end())
630  {
632  boost::str(boost::format("Trying to connect a counter to a device that is "
633  "not registered (device UID %1%)")
634  % deviceUid));
635  }
636 
637  // Get the associated device
638  const DevicePtr& device = deviceIt->second;
639  BOOST_ASSERT(device);
640 
641  // Get the number of cores of the associated device
642  return device->m_Cores;
643  }
644 
645  // 3. If neither of the above is set then look at the category to see if it has a device associated with it,
646  // if it does then take that device's numberOfCores as the deviceCores value
647  uint16_t parentCategoryDeviceUid = parentCategory->m_DeviceUid;
648  if (parentCategoryDeviceUid > 0)
649  {
650  // Check that the device associated to the parent category is already registered
651  auto deviceIt = FindDevice(parentCategoryDeviceUid);
652  if (deviceIt == m_Devices.end())
653  {
655  boost::str(boost::format("Trying to get the number of cores from a device that is "
656  "not registered (device UID %1%)")
657  % parentCategoryDeviceUid));
658  }
659 
660  // Get the associated device
661  const DevicePtr& device = deviceIt->second;
662  BOOST_ASSERT(device);
663 
664  // Get the number of cores of the device associated to the parent category
665  return device->m_Cores;
666  }
667 
668  // 4. If none of the above holds then set deviceCores to zero
669  return 0;
670 }
671 
672 } // namespace profiling
673 
674 } // namespace armnn
Counters::const_iterator CountersIt
std::unique_ptr< CounterSet > CounterSetPtr
const CounterSet * GetCounterSet(uint16_t uid) const override
uint16_t GetNextUid(bool peekOnly)
const Device * GetDevice(uint16_t uid) const override
const Category * RegisterCategory(const std::string &categoryName, const Optional< uint16_t > &deviceUid=EmptyOptional(), const Optional< uint16_t > &counterSetUid=EmptyOptional()) override
bool IsCategoryRegistered(const std::string &categoryName) const
const Device * RegisterDevice(const std::string &deviceName, uint16_t cores=0, const Optional< std::string > &parentCategoryName=EmptyOptional()) override
bool IsCounterSetRegistered(uint16_t counterSetUid) const
const Counter * GetCounter(uint16_t uid) const override
const Counter * RegisterCounter(const BackendId &backendId, const uint16_t uid, const std::string &parentCategoryName, uint16_t counterClass, uint16_t interpolation, double multiplier, const std::string &name, const std::string &description, const Optional< std::string > &units=EmptyOptional(), const Optional< uint16_t > &numberOfCores=EmptyOptional(), const Optional< uint16_t > &deviceUid=EmptyOptional(), const Optional< uint16_t > &counterSetUid=EmptyOptional()) override
std::vector< uint16_t > GetNextCounterUids(uint16_t firstUid, uint16_t cores)
bool IsDeviceRegistered(uint16_t deviceUid) const
std::shared_ptr< Counter > CounterPtr
CounterSets::const_iterator CounterSetsIt
std::unique_ptr< Category > CategoryPtr
std::unique_ptr< Device > DevicePtr
Devices::const_iterator DevicesIt
Categories::const_iterator CategoriesIt
const Category * GetCategory(const std::string &name) const override
const CounterSet * RegisterCounterSet(const std::string &counterSetName, uint16_t count=0, const Optional< std::string > &parentCategoryName=EmptyOptional()) override
bool IsCounterRegistered(uint16_t counterUid) const
bool has_value() const noexcept
Definition: Optional.hpp:53