Spicy
type_erase.h
1 // Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details.
2 //
3 // Machinery for creating type-erased interface classes with value semantics.
4 // Needs help through an external Python script generating a bunch of boiler
5 // plate code.
6 
7 #pragma once
8 
9 #include <memory>
10 #include <optional>
11 #include <string>
12 #include <type_traits>
13 #include <unordered_map>
14 #include <utility>
15 
16 #include <hilti/base/intrusive-ptr.h>
17 #include <hilti/base/optional-ref.h>
18 #include <hilti/base/util.h>
19 
20 namespace hilti::util::type_erasure {
21 
22 // If this defined, we track the number of type-erased instances by their
23 // actual type, and then print out a summary of the top types at the end as
24 // part of the timing summary.
25 //
26 // #define HILTI_TYPE_ERASURE_PROFILE
27 
28 namespace trait {
29 class TypeErased {};
30 class Singleton {};
31 } // namespace trait
32 
33 namespace detail {
34 
35 #ifdef HILTI_TYPE_ERASURE_PROFILE
36 struct Counters {
37  int64_t max = 0;
38  int64_t current = 0;
39 
40  void increment() {
41  ++max;
42  ++current;
43  }
44  void decrement() { --current; }
45 };
46 
47 inline auto& instance_counters() {
48  static std::unordered_map<std::string, Counters> global_counters;
49  return global_counters;
50 }
51 #endif
52 
53 } // namespace detail
54 
55 extern void summary(std::ostream& out);
56 
59 public:
60  virtual const std::type_info& typeid_() const = 0;
61  virtual std::string typename_() const = 0;
62  virtual uintptr_t identity() const = 0; // Returns unique identity of current value
63 
64  // For internal use only.
65  virtual std::pair<const ConceptBase*, const void*> _childAs(const std::type_info& ti) const = 0;
66  virtual std::pair<ConceptBase*, void*> _childAs(const std::type_info& ti) = 0;
67 };
68 
70 template<typename T, typename Concept, typename... ConceptArgs>
71 class ModelBase : public Concept {
72 public:
73  ModelBase(T data, ConceptArgs&&... args) : Concept(std::forward<ConceptArgs>(args)...), _data(std::move(data)) {
74 #ifdef HILTI_TYPE_ERASURE_PROFILE
75  detail::instance_counters()[typeid(T).name()].increment();
76 #endif
77  }
78 
79  ~ModelBase() override {
80 #ifdef HILTI_TYPE_ERASURE_PROFILE
81  detail::instance_counters()[typeid(T).name()].decrement();
82 #endif
83  }
84 
85  ModelBase() = delete;
86  ModelBase(const ModelBase&) = default;
87  ModelBase(ModelBase&&) = default;
88  ModelBase& operator=(const ModelBase&) = default;
89  ModelBase& operator=(ModelBase&&) = default;
90 
91 
92  const T& data() const { return this->_data; }
93  T& data() { return _data; }
94 
95  uintptr_t identity() const final {
96  if constexpr ( std::is_base_of<trait::TypeErased, T>::value )
97  return _data.data()->identity(); // NOLINT
98 
99  return reinterpret_cast<uintptr_t>(&_data);
100  }
101 
102  const std::type_info& typeid_() const final { return typeid(T); }
103 
104  std::string typename_() const final {
105  // Get the inner name if we store a type erased type in turn.
106  if constexpr ( std::is_base_of<trait::TypeErased, T>::value )
107  return data().typename_(); // NOLINT
108 
109  return hilti::util::typename_<T>();
110  }
111 
112  std::pair<const ConceptBase*, const void*> _childAs(const std::type_info& ti) const final {
113  const ConceptBase* base = nullptr;
114 
115  if constexpr ( std::is_base_of<trait::TypeErased, T>::value )
116  base = _data.data().get(); //NOLINT
117 
118  if ( typeid(_data) == ti )
119  return {base, &_data};
120 
121  return {base, nullptr};
122  }
123 
124  std::pair<ConceptBase*, void*> _childAs(const std::type_info& ti) final {
125  ConceptBase* base = nullptr;
126 
127  if constexpr ( std::is_base_of<trait::TypeErased, T>::value )
128  base = _data.data().get(); //NOLINT
129 
130  if ( typeid(_data) == ti )
131  return {base, &_data};
132 
133  return {base, nullptr};
134  }
135 
136 private:
137  T _data;
138 };
139 
141 template<typename Trait, typename Concept, template<typename T> typename Model, typename... ConceptArgs>
143 public:
144  ErasedBase() = default;
145  ErasedBase(const ErasedBase& other) = default;
146  ErasedBase(ErasedBase&& other) noexcept = default;
147  ErasedBase& operator=(const ErasedBase& other) = default;
148  ErasedBase& operator=(ErasedBase&& other) noexcept = default;
149  virtual ~ErasedBase() = default; // Make class polymorphic
150 
151  template<typename T, IF_DERIVED_FROM(T, Trait)>
152  ErasedBase(T t, ConceptArgs&&... args)
153  : _data(make_intrusive<Model<T>>(std::move(t), std::forward<ConceptArgs>(args)...)) {}
154 
155  ErasedBase& operator=(IntrusivePtr<Concept> data) {
156  _data = std::move(data);
157  ;
158  return *this;
159  }
160 
166  const std::type_info& typeid_() const {
167  assert(_data);
168  return _data->typeid_();
169  }
170 
176  std::string typename_() const { return _data ? _data->typename_() : "<nullptr>"; }
177 
182  template<typename T>
183  const T& as() const {
184  if ( auto p = _tryAs<T>() )
185  return *p;
186 
187  std::cerr << hilti::util::fmt("internal error: unexpected type, want %s but have %s",
188  hilti::util::typename_<T>(), typename_())
189  << std::endl;
191  }
192 
197  template<typename T>
198  T& as() {
199  if ( auto p = _tryAs<T>() )
200  return *p;
201 
202  std::cerr << hilti::util::fmt("internal error: unexpected type, want %s but have %s",
203  hilti::util::typename_<T>(), typename_())
204  << std::endl;
206  }
207 
212  template<typename T>
213  bool isA() const {
214  return _tryAs<T>() != nullptr;
215  }
216 
218  template<typename T>
220  if ( auto p = _tryAs<T>() )
221  return *p;
222 
223  return {};
224  }
225 
227  auto& data() const { return _data; }
228 
230  auto& data() { return _data; }
231 
233  uintptr_t identity() const { return _data ? _data->identity() : 0; }
234 
235 private:
236  template<typename T>
237  const T* _tryAs() const {
238  if constexpr ( std::is_base_of<ErasedBase, T>::value )
239  return static_cast<const T*>(this);
240 
241  if ( typeid(Model<T>) == typeid(*_data) )
242  return &(::hilti::cast_intrusive<const Model<T>>(_data))->data();
243 
244  std::pair<const ConceptBase*, const void*> c = {_data.get(), nullptr};
245 
246  while ( c.first ) {
247  c = c.first->_childAs(typeid(T));
248 
249  if ( c.second )
250  return static_cast<const T*>(c.second);
251  }
252 
253  return nullptr;
254  }
255 
256  template<typename T>
257  T* _tryAs() {
258  if constexpr ( std::is_base_of<ErasedBase, T>::value )
259  return static_cast<T*>(this);
260 
261  if ( typeid(Model<T>) == typeid(*_data) )
262  return &(hilti::cast_intrusive<Model<T>>(_data))->data();
263 
264  std::pair<ConceptBase*, void*> c = {_data.get(), nullptr};
265 
266  while ( c.first ) {
267  c = c.first->_childAs(typeid(T));
268 
269  if ( c.second )
270  return static_cast<T*>(c.second);
271  }
272 
273  return nullptr;
274  }
275 
276  // See https://stackoverflow.com/questions/18709647/shared-pointer-to-an-immutable-type-has-value-semantics
277  IntrusivePtr<Concept> _data;
278 };
279 
280 } // namespace hilti::util::type_erasure
std::string typename_() const
Definition: type_erase.h:176
Definition: type_erase.h:71
const T & as() const
Definition: type_erase.h:183
std::string typename_()
Definition: util.h:74
void abort_with_backtrace()
Definition: util.cc:180
Definition: intrusive-ptr.h:28
uintptr_t identity() const
Definition: type_erase.h:233
auto & data() const
Definition: type_erase.h:227
const std::type_info & typeid_() const
Definition: type_erase.h:166
Definition: optional-ref.h:22
T & as()
Definition: type_erase.h:198
std::string fmt(const char *fmt, const Args &... args)
Definition: util.h:80
Definition: intrusive-ptr.h:69
bool isA() const
Definition: type_erase.h:213
Definition: type_erase.h:142
auto & data()
Definition: type_erase.h:230
Definition: type_erase.h:20
Definition: type_erase.h:58
optional_ref< const T > tryAs() const
Definition: type_erase.h:219