wxMaxima
CellPtr.h
Go to the documentation of this file.
1 // -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode: nil -*-
2 //
3 // Copyright (C) 2020 Kuba Ober <kuba@bertec.com>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 //
20 // SPDX-License-Identifier: GPL-2.0+
21 
46 #ifndef CELLPTR_H
47 #define CELLPTR_H
48 
49 #include <wx/debug.h>
50 #include <wx/log.h>
51 #include <cstddef>
52 #include <cstdint>
53 #include <cinttypes>
54 #include <memory>
55 #include <type_traits>
56 
58 #define CELLPTR_CAST_TO_PTR 1
59 
61 #ifndef CELLPTR_COUNT_INSTANCES
62 #define CELLPTR_COUNT_INSTANCES 0
63 #endif
64 
66 #ifndef CELLPTR_LOG_REFS
67 #define CELLPTR_LOG_REFS 0
68 #endif
69 
71 #ifndef CELLPTR_LOG_INSTANCES
72 #define CELLPTR_LOG_INSTANCES 0
73 #endif
74 
76 #ifndef CELLPTR_LOG_METHOD
77 #define CELLPTR_LOG_METHOD wxLogDebug
78 #endif
79 
80 class CellPtrBase;
81 
86 class Observed
87 {
88  class ControlBlock final
89  {
91  Observed *m_object = {};
93  unsigned int m_refCount = 0;
95  static size_t m_instanceCount;
96 
97  #if CELLPTR_LOG_REFS
98  void LogConstruct(const Observed *) const;
99  void LogRef(const CellPtrBase *) const;
100  void LogDeref(const CellPtrBase *) const;
101  void LogDestruct() const;
102  #else
103  void LogConstruct(const Observed *) const {}
104  void LogRef(const CellPtrBase *) const {}
105  void LogDeref(const CellPtrBase *) const {}
106  void LogDestruct() const {}
107  #endif
108 
109  public:
110  explicit ControlBlock(Observed *object) : m_object(object)
111  {
112  #if CELLPTR_COUNT_INSTANCES
113  ++ m_instanceCount;
114  #endif
115  LogConstruct(object);
116  wxASSERT(object);
117  }
118  ~ControlBlock() {
119  #if CELLPTR_COUNT_INSTANCES
120  -- m_instanceCount;
121  #endif
122  LogDestruct();
123  }
124  ControlBlock(const ControlBlock &) = delete;
125  void operator=(const ControlBlock &) = delete;
126 
127  void reset() noexcept { m_object = nullptr; }
128  inline Observed *Get() const noexcept { return m_object; }
129 
131  ControlBlock *Ref(const CellPtrBase *cellptr)
132  {
133  LogRef(cellptr);
134  ++m_refCount;
135  return this;
136  }
137 
140  bool Deref(const CellPtrBase *cellptr)
141  {
142  LogDeref(cellptr);
143  wxASSERT(m_refCount >= 1);
144  return --m_refCount;
145  }
146 
147  static size_t GetLiveInstanceCount() { return m_instanceCount; }
148  };
149 
150  static_assert(alignof(ControlBlock) >= 4, "Observed::ControlBlock doesn't have minimum viable alignment");
151 
158  class CellPtrImplPointer final
159  {
160  friend void swap(CellPtrImplPointer &a, CellPtrImplPointer &b) noexcept;
161  uintptr_t m_ptr = {};
162  enum Tag : uintptr_t {
163  to_Observed = 0,
164  to_ControlBlock = 1,
165  to_CellPtrBase = 2,
166  to_MASK = 3,
167  to_MASK_OUT = uintptr_t(-intptr_t(to_MASK + 1)),
168  };
169  static uintptr_t ReprFor(Observed *ptr) noexcept
170  { return reinterpret_cast<uintptr_t>(ptr) | to_Observed; }
171  static uintptr_t ReprFor(ControlBlock *ptr) noexcept
172  { return reinterpret_cast<uintptr_t>(ptr) | to_ControlBlock; }
173  static uintptr_t ReprFor(CellPtrBase *ptr) noexcept
174  { return reinterpret_cast<uintptr_t>(ptr) | to_CellPtrBase; }
175  public:
176  constexpr CellPtrImplPointer() noexcept {}
177  constexpr CellPtrImplPointer(const CellPtrImplPointer &) noexcept = default;
178  // cppcheck-suppress noExplicitConstructor
179  constexpr CellPtrImplPointer(decltype(nullptr)) noexcept {}
180  // cppcheck-suppress noExplicitConstructor
181  CellPtrImplPointer(Observed *ptr) noexcept : m_ptr(ReprFor(ptr)) {}
182  // cppcheck-suppress noExplicitConstructor
183  CellPtrImplPointer(ControlBlock *ptr) noexcept : m_ptr(ReprFor(ptr)) {}
184  // cppcheck-suppress noExplicitConstructor
185  CellPtrImplPointer(CellPtrBase *ptr) noexcept : m_ptr(ReprFor(ptr)) {}
186 
187  constexpr CellPtrImplPointer &operator=(decltype(nullptr)) noexcept { m_ptr = {}; return *this; }
188  constexpr CellPtrImplPointer &operator=(CellPtrImplPointer o) noexcept { m_ptr = o.m_ptr; return *this; }
189  CellPtrImplPointer &operator=(Observed *ptr) noexcept { m_ptr = ReprFor(ptr); return *this; }
190  CellPtrImplPointer &operator=(ControlBlock *ptr) noexcept { m_ptr = ReprFor(ptr); return *this; }
191  CellPtrImplPointer &operator=(CellPtrBase *ptr) noexcept { m_ptr = ReprFor(ptr); return *this; }
192 
193  constexpr explicit inline operator bool() const noexcept { return m_ptr != 0; }
194  constexpr inline bool HasObserved() const noexcept { return (m_ptr & to_MASK) == to_Observed; }
195  constexpr inline bool HasControlBlock() const noexcept { return (m_ptr & to_MASK) == to_ControlBlock; }
196  constexpr inline bool HasCellPtrBase() const noexcept { return (m_ptr & to_MASK) == to_CellPtrBase; }
197  constexpr inline auto GetObserved() const noexcept { static_assert((to_Observed & to_MASK_OUT) == 0, "");
198  return HasObserved() ? reinterpret_cast<Observed *> (m_ptr) : nullptr; }
199  constexpr inline auto GetControlBlock() const noexcept { return HasControlBlock() ? reinterpret_cast<ControlBlock *>(m_ptr & to_MASK_OUT) : nullptr; }
200  constexpr inline auto GetCellPtrBase() const noexcept { return HasCellPtrBase() ? reinterpret_cast<CellPtrBase *> (m_ptr & to_MASK_OUT) : nullptr; }
201 
202  constexpr inline auto GetImpl() const noexcept { return m_ptr; }
203  inline auto CastAsObserved() const noexcept { return reinterpret_cast<Observed *>(m_ptr); }
204  inline auto CastAsControlBlock() const noexcept { return reinterpret_cast<ControlBlock *>(m_ptr & to_MASK_OUT); }
205  };
206 
207  friend void swap(CellPtrImplPointer &a, CellPtrImplPointer &b) noexcept;
208  friend class CellPtrBase;
209  static size_t m_instanceCount;
210 
219  CellPtrImplPointer m_ptr;
220 
221  Observed(const Observed &) = delete;
222  void operator=(const Observed &) = delete;
224  void OnEndOfLife() noexcept;
225 
226 #if CELLPTR_LOG_REFS
227  void LogRef(const CellPtrBase *) const;
228  void LogDeref(const CellPtrBase *) const;
229 #else
230  void LogRef(const CellPtrBase *) const {}
231  void LogDeref(const CellPtrBase *) const {}
232 #endif
233 
234 protected:
235  Observed() noexcept
236 #if CELLPTR_COUNT_INSTANCES
237  { ++ m_instanceCount; }
238 #else
239  = default;
240 #endif
241  ~Observed()
242  {
243  if (m_ptr) OnEndOfLife();
244  #if CELLPTR_COUNT_INSTANCES
245  -- m_instanceCount;
246  #endif
247  }
248 
249 public:
250  static size_t GetLiveInstanceCount() { return m_instanceCount; }
251  static size_t GetLiveControlBlockInstanceCount() { return ControlBlock::GetLiveInstanceCount(); }
252  bool IsNull() const { return !m_ptr.GetImpl(); }
253  bool HasControlBlock() const { return m_ptr.GetControlBlock(); }
254  bool HasOneCellPtr() const { return m_ptr.GetCellPtrBase(); }
255 };
256 
257 inline void swap(Observed::CellPtrImplPointer &a, Observed::CellPtrImplPointer &b) noexcept
258 { std::swap(a.m_ptr, b.m_ptr); }
259 
260 class Cell;
261 class GroupCell;
262 
273 {
274  using CellPtrImplPointer = Observed::CellPtrImplPointer;
275  using ControlBlock = Observed::ControlBlock;
276  static size_t m_instanceCount;
277 
286  mutable CellPtrImplPointer m_ptr;
287 
289  void Ref(Observed *obj);
290 
292  void Deref() noexcept;
293 
296  decltype(nullptr) DerefControlBlock() const noexcept;
297 
298 #if CELLPTR_LOG_INSTANCES
299  void LogConstruction(Observed *obj) const;
300  void LogMove(const CellPtrBase &o) const;
301  void LogAssignment(const CellPtrBase &o) const;
302  void LogDestruction() const;
303 #else
304  inline void LogConstruction(Observed *) const {}
305  inline void LogMove(const CellPtrBase &) const {}
306  inline void LogAssignment(const CellPtrBase &) const {}
307  inline void LogDestruction() const {}
308 #endif
309 
310 protected:
311  explicit CellPtrBase(Observed *obj = nullptr) noexcept
312  {
313  #if CELLPTR_COUNT_INSTANCES
314  ++m_instanceCount;
315  #endif
316  if (obj) Ref(obj);
317  LogConstruction(obj);
318  }
319 
320  CellPtrBase(const CellPtrBase &o) noexcept : CellPtrBase(o.base_get()) {}
321 
322  CellPtrBase(CellPtrBase &&o) noexcept
323  {
324  #if CELLPTR_COUNT_INSTANCES
325  ++m_instanceCount;
326  #endif
327  LogMove(o);
328  using namespace std;
329 
330  auto *thisObserved = m_ptr.GetObserved();
331  if (thisObserved)
332  {
333  wxASSERT(thisObserved->m_ptr.GetCellPtrBase() == this);
334  thisObserved->LogDeref(this);
335  thisObserved->LogRef(&o);
336  thisObserved->m_ptr = &o;
337  }
338 
339  auto *otherObserved = o.m_ptr.GetObserved();
340  if (otherObserved)
341  {
342  wxASSERT(otherObserved->m_ptr.GetCellPtrBase() == &o);
343  otherObserved->LogDeref(&o);
344  otherObserved->LogRef(this);
345  otherObserved->m_ptr = this;
346  }
347 
348  swap(m_ptr, o.m_ptr);
349  }
350 
351  ~CellPtrBase() noexcept
352  {
353  #if CELLPTR_COUNT_INSTANCES
354  --m_instanceCount;
355  #endif
356  LogDestruction();
357  Deref();
358  }
359 
360  CellPtrBase &operator=(const CellPtrBase &o) noexcept
361  {
362  base_reset(o.base_get());
363  return *this;
364  }
365 
366  CellPtrBase &operator=(CellPtrBase &&o) noexcept
367  {
368  LogAssignment(o);
369  using namespace std;
370  swap(m_ptr, o.m_ptr);
371  return *this;
372  }
373 
374  inline Observed *base_get() const noexcept
375  {
376  // Warning: This function is CRITICAL to the performance of wxMaxima
377  // as a whole!
378  //
379  // The common hot path that iterates cells via the m_nextToDraw uses
380  // this function and is intimately tied to its performance. Small
381  // changes here can cause performance regressions - or small performance
382  // improvements.
383  //
384  // If you change anything, do before- and after- measurements to verify
385  // that whatever improvement you sought is in fact achieved. Changes that
386  // don't measurably improve performance are discouraged.
387 
388  // The common path, meant to be hot: if we point directly at the observed object,
389  // just return that. This is also where null is returned if the pointer is null.
390  // `HasObserved()` is a simple bitmask test, and `CastAsObserved` is a binary
391  // NO-OP.
392 
393  if (m_ptr.HasObserved())
394  return m_ptr.CastAsObserved();
395 
396  // Otherwise, we must be pointing to a control block: get the pointed-to
397  // observed from the control block. Since such use case is meant to be
398  // rare, the overhead of pointer chasing (one extra layer of indirection)
399  // is acceptable.
400 
401  auto *const observed = m_ptr.CastAsControlBlock()->Get();
402  if (observed)
403  return observed;
404 
405  // We have a control block, but the observed object is gone: we dereference
406  // the zombie control block, to deallocate it as soon as possible, and we
407  // reset ourselves to null. This happens only once per observed object, and
408  // subsequent calls will go via the common path.
409 
410  return DerefControlBlock(); // returns null - allows a tail call optimization
411  }
412 
413  void base_reset(Observed *obj = nullptr) noexcept;
414 
415 public:
416  template <typename U>
417  static bool constexpr is_pointer() {
418  return std::is_same<U, decltype(nullptr)>::value
419  || (std::is_pointer<U>::value && std::is_convertible<U, Observed*>::value);
420  }
421 
422  explicit operator bool() const noexcept { return base_get(); }
423 
424  inline void reset() noexcept { base_reset(); }
425 
427  auto cmpPointers(const CellPtrBase &o) const noexcept { return m_ptr.GetImpl() - o.m_ptr.GetImpl(); }
428 
430  auto cmpObjects(const CellPtrBase &o) const noexcept { return base_get() - o.base_get(); }
431 
433  auto cmpObjects(const Observed *o) const noexcept { return base_get() - o; }
434 
435  static size_t GetLiveInstanceCount() noexcept { return m_instanceCount; }
436 
437  bool IsNull() const { return !m_ptr.GetImpl(); }
438  bool HasOneObserved() const { return m_ptr.GetObserved(); }
439  bool HasControlBlock() const { return m_ptr.GetControlBlock(); }
440 };
441 
442 static_assert(alignof(Observed) >= 4, "Observed doesn't have minimum viable alignment");
443 static_assert(alignof(CellPtrBase) >= 4, "CellPtrBase doesn't have minimum viable alignment");
444 
474 template <typename T>
475 class CellPtr final : public CellPtrBase
476 {
477  template <typename U>
478  static bool constexpr is_pointer() {
479  return std::is_same<U, decltype(nullptr)>::value
480  || (std::is_pointer<U>::value && std::is_convertible<U, pointer>::value);
481  }
482 public:
483  using value_type = T;
484  using pointer = T*;
485  using const_pointer = const T*;
486  using reference = T&;
487 
488  CellPtr() noexcept = default;
489 
490  // Observers
491  //
492  pointer get() const noexcept;
493  inline reference operator*() const noexcept { return *get(); }
494  inline pointer operator->() const noexcept { return get(); }
495 
496 #if CELLPTR_CAST_TO_PTR
497  operator pointer() const noexcept { return get(); }
498 #endif
499 
500  template <typename PtrT, typename std::enable_if<std::is_pointer<PtrT>::value, bool>::type = true>
501  PtrT CastAs() const noexcept;
502 
503  // Operations with NULL and integers in general
504  //
505  explicit CellPtr(int) = delete;
506  explicit CellPtr(void *) = delete;
507 
508  // Operations with nullptr_t
509  //
510  void reset() noexcept { base_reset(); }
511  explicit CellPtr(decltype(nullptr)) noexcept {}
512  CellPtr &operator=(decltype(nullptr)) noexcept { base_reset(); return *this; }
513  bool operator==(decltype(nullptr)) const noexcept { return !bool(*this); }
514  bool operator!=(decltype(nullptr)) const noexcept { return bool(*this); }
515 
516  // Operations with convertible-to-pointer types
517  //
518  template <typename U, typename std::enable_if<is_pointer<U>(), bool>::type = true>
519  explicit CellPtr(U obj) noexcept : CellPtrBase(obj) {}
520 
521  template <typename U, typename std::enable_if<is_pointer<U>(), bool>::type = true>
522  CellPtr &operator=(U obj) noexcept
523  {
524  base_reset(obj);
525  return *this;
526  }
527 
528  template <typename U, typename std::enable_if<is_pointer<U>(), bool>::type = true>
529  void reset(U obj) noexcept
530  { base_reset(obj); }
531  // Operations with compatible CellPtrs
532  //
533  CellPtr(CellPtr &o) noexcept : CellPtrBase(o) {}
534  CellPtr(CellPtr &&o) noexcept : CellPtrBase(std::move(o)) {}
535  CellPtr &operator=(const CellPtr &o) noexcept { CellPtrBase::operator=(o); return *this; }
536  CellPtr &operator=(CellPtr &&o) noexcept { CellPtrBase::operator=(std::move(o)); return *this; }
537 
538  template <typename U,
539  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
540  // cppcheck-suppress noExplicitConstructor
541  CellPtr(CellPtr<U> &&o) noexcept : CellPtrBase(o) {}
542 
543  template <typename U,
544  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
545  // cppcheck-suppress noExplicitConstructor
546  CellPtr(const CellPtr<U> &o) noexcept : CellPtrBase(o.get()) {}
547 
548  template <typename U,
549  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
550  CellPtr &operator=(CellPtr<U> &&o) noexcept
551  {
552  CellPtrBase::operator=(o);
553  return *this;
554  }
555  template <typename U,
556  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
557  CellPtr &operator=(const CellPtr<U> &o) noexcept
558  {
559  CellPtrBase::operator=(o);
560  return *this;
561  }
562 
563 #if !CELLPTR_CAST_TO_PTR
564  template <typename U,
565  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
566  bool operator==(const CellPtr<U> &ptr) const noexcept { return cmpControlBlocks(ptr) == 0; }
567  template <typename U,
568  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
569  bool operator!=(const CellPtr<U> &ptr) const noexcept { return cmpControlBlocks(ptr) != 0; }
570  template <typename U,
571  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
572  bool operator<(const CellPtr<U> &ptr) const noexcept { return cmpObjects(ptr) < 0; }
573 #endif
574 
575  // Operations with compatible unique_ptr
576  //
577  template <typename U, typename Del>
578  explicit CellPtr(std::unique_ptr<U, Del> &&) = delete;
579 
580  template <typename U, typename Del,
581  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
582  explicit CellPtr(const std::unique_ptr<U, Del> &ptr) noexcept : CellPtrBase(ptr.get()) {}
583 
584  template <typename U, typename Del>
585  CellPtr &operator=(std::unique_ptr<U, Del> &&) = delete;
586 
587  template <typename U, typename Del,
588  typename std::enable_if<std::is_convertible<typename std::add_pointer<U>::type, pointer>::value, bool>::type = true>
589  CellPtr &operator=(const std::unique_ptr<U, Del> &o) noexcept
590  { return *this = o.get(); }
591 };
592 
593 //
594 
595 template <typename T> typename
596 CellPtr<T>::pointer CellPtr<T>::get() const noexcept { return static_cast<pointer>(base_get()); }
597 
602 template <>
604 
605 //
606 
607 template <typename T, typename U>
608 bool operator==(const CellPtr<T> &left, const CellPtr<U> &right) noexcept { return left.cmpPointers(right) == 0; }
609 
610 #if !CELLPTR_CAST_TO_PTR
611 template <typename T, typename U,
612  typename std::enable_if<CellPtrBase::is_pointer<U>(), bool>::type = true>
613 bool operator==(U left, const CellPtr<T> &right) noexcept { return right.cmpObjects(left) == 0; }
614 #endif
615 
616 template <typename T, typename U,
617  typename std::enable_if<CellPtrBase::is_pointer<U>(), bool>::type = true>
618 bool operator==(const CellPtr<T> &left, U right) noexcept { return left.cmpObjects(right) == 0; }
619 
620 template <typename T, typename U>
621 bool operator!=(const CellPtr<T> &left, const CellPtr<U> &right) noexcept { return left.cmpPointers(right) != 0; }
622 
623 #if !CELLPTR_CAST_TO_PTR
624 template <typename T, typename U,
625  typename std::enable_if<CellPtrBase::is_pointer<U>(), bool>::type = true>
626 bool operator!=(U left, const CellPtr<T> &right) noexcept { return right.cmpObjects(left) != 0; }
627 #endif
628 
629 template <typename T, typename U,
630  typename std::enable_if<CellPtrBase::is_pointer<U>(), bool>::type = true>
631 bool operator!=(const CellPtr<T> &left, U right) noexcept { return left.cmpObjects(right) != 0; }
632 
633 template <typename T, typename U>
634 bool operator<(const CellPtr<T> &left, const CellPtr<U> &right) noexcept { return left.cmpObjects(right) < 0; }
635 
636 #if !CELLPTR_CAST_TO_PTR
637 template <typename T, typename U,
638  typename std::enable_if<CellPtrBase::is_pointer<U>(), bool>::type = true>
639 bool operator<(U left, const CellPtr<T> &right) noexcept { return right.cmpObjects(left) > 0; }
640 #endif
641 
642 template <typename T, typename U,
643  typename std::enable_if<CellPtrBase::is_pointer<U>(), bool>::type = true>
644 bool operator<(const CellPtr<T> &left, U right) noexcept { return left.cmpObjects(right) < 0; }
645 
646 //
647 
650 template<typename Derived, typename Base>
651 std::unique_ptr<Derived> static_unique_ptr_cast(std::unique_ptr<Base>&& p) noexcept
652 {
653  auto d = static_cast<Derived *>(p.release());
654  return std::unique_ptr<Derived>(d);
655  // Note: We don't move the deleter, since it's not special.
656 }
657 
660 template<typename Derived, typename Base>
661 std::unique_ptr<Derived> dynamic_unique_ptr_cast(std::unique_ptr<Base>&& p) noexcept
662 {
663  auto d = dynamic_cast<Derived *>(p.get());
664  if (d)
665  p.release();
666  return std::unique_ptr<Derived>(d);
667  // Note: We don't move the deleter, since it's not special.
668 }
669 
670 #endif // CELLPTR_H
CellPtr
Definition: CellPtr.h:475
Observed
Definition: CellPtr.h:86
CellPtrBase::cmpObjects
auto cmpObjects(const Observed *o) const noexcept
This is the spaceship operator acting on pointed-to objects.
Definition: CellPtr.h:433
dynamic_unique_ptr_cast
std::unique_ptr< Derived > dynamic_unique_ptr_cast(std::unique_ptr< Base > &&p) noexcept
Definition: CellPtr.h:661
CellPtrBase::base_reset
void base_reset(Observed *obj=nullptr) noexcept
Definition: CellPtr.cpp:158
CellPtrBase
Definition: CellPtr.h:272
Cell
Definition: Cell.h:139
CellPtrBase::cmpObjects
auto cmpObjects(const CellPtrBase &o) const noexcept
This is the spaceship operator acting on pointed-to objects.
Definition: CellPtr.h:430
CellPtrBase::cmpPointers
auto cmpPointers(const CellPtrBase &o) const noexcept
This is exactly like the spaceship operator in C++20.
Definition: CellPtr.h:427
GroupCell
Definition: GroupCell.h:68
static_unique_ptr_cast
std::unique_ptr< Derived > static_unique_ptr_cast(std::unique_ptr< Base > &&p) noexcept
Definition: CellPtr.h:651