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  explicit ErasedBase(IntrusivePtr<Concept> data) : _data(std::move(data)) {}
156  ErasedBase& operator=(IntrusivePtr<Concept> data) {
157  _data = std::move(data);
158  ;
159  return *this;
160  }
161 
167  const std::type_info& typeid_() const {
168  assert(_data);
169  return _data->typeid_();
170  }
171 
177  std::string typename_() const { return _data ? _data->typename_() : "<nullptr>"; }
178 
183  template<typename T>
184  const T& as() const {
185  if ( auto p = _tryAs<T>() )
186  return *p;
187 
188  std::cerr << hilti::util::fmt("internal error: unexpected type, want %s but have %s",
189  hilti::util::typename_<T>(), typename_())
190  << std::endl;
192  }
193 
198  template<typename T>
199  T& as() {
200  if ( auto p = _tryAs<T>() )
201  return *p;
202 
203  std::cerr << hilti::util::fmt("internal error: unexpected type, want %s but have %s",
204  hilti::util::typename_<T>(), typename_())
205  << std::endl;
207  }
208 
213  template<typename T>
214  bool isA() const {
215  return _tryAs<T>() != nullptr;
216  }
217 
219  template<typename T>
220  std::optional<T> tryAs() const {
221  if ( auto p = _tryAs<T>() )
222  return *p;
223 
224  return {};
225  }
226 
228  template<typename T>
230  if ( auto p = _tryAs<T>() )
231  return *p;
232 
233  return {};
234  }
235 
237  auto& data() const { return _data; }
238 
240  auto& data() { return _data; }
241 
243  uintptr_t identity() const { return _data ? _data->identity() : 0; }
244 
245 private:
246  template<typename T>
247  const T* _tryAs() const {
248  if constexpr ( std::is_base_of<ErasedBase, T>::value )
249  return static_cast<const T*>(this);
250 
251  if ( typeid(Model<T>) == typeid(*_data) )
252  return &(::hilti::cast_intrusive<const Model<T>>(_data))->data();
253 
254  std::pair<const ConceptBase*, const void*> c = {_data.get(), nullptr};
255 
256  while ( c.first ) {
257  c = c.first->_childAs(typeid(T));
258 
259  if ( c.second )
260  return static_cast<const T*>(c.second);
261  }
262 
263  return nullptr;
264  }
265 
266  template<typename T>
267  T* _tryAs() {
268  if constexpr ( std::is_base_of<ErasedBase, T>::value )
269  return static_cast<T*>(this);
270 
271  if ( typeid(Model<T>) == typeid(*_data) )
272  return &(hilti::cast_intrusive<Model<T>>(_data))->data();
273 
274  std::pair<ConceptBase*, void*> c = {_data.get(), nullptr};
275 
276  while ( c.first ) {
277  c = c.first->_childAs(typeid(T));
278 
279  if ( c.second )
280  return static_cast<T*>(c.second);
281  }
282 
283  return nullptr;
284  }
285 
286  // See https://stackoverflow.com/questions/18709647/shared-pointer-to-an-immutable-type-has-value-semantics
287  IntrusivePtr<Concept> _data;
288 };
289 
290 } // namespace hilti::util::type_erasure
std::string typename_() const
Definition: type_erase.h:177
Definition: type_erase.h:71
const T & as() const
Definition: type_erase.h:184
std::string typename_()
Definition: util.h:74
std::optional< T > tryAs() const
Definition: type_erase.h:220
void abort_with_backtrace()
Definition: util.cc:180
Definition: intrusive-ptr.h:28
uintptr_t identity() const
Definition: type_erase.h:243
auto & data() const
Definition: type_erase.h:237
const std::type_info & typeid_() const
Definition: type_erase.h:167
Definition: optional-ref.h:22
T & as()
Definition: type_erase.h:199
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:214
Definition: type_erase.h:142
auto & data()
Definition: type_erase.h:240
Definition: type_erase.h:20
optional_ref< const T > tryReferenceAs() const
Definition: type_erase.h:229
Definition: type_erase.h:58