2c57fb6ee75ccc7c1447166873e774f3c38e4b2f
[folly.git] / folly / DiscriminatedPtr.h
1 /*
2  * Copyright 2017 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * Discriminated pointer: Type-safe pointer to one of several types.
19  *
20  * Similar to boost::variant, but has no space overhead over a raw pointer, as
21  * it relies on the fact that (on x86_64) there are 16 unused bits in a
22  * pointer.
23  *
24  * @author Tudor Bosman (tudorb@fb.com)
25  */
26
27 #pragma once
28
29 #include <limits>
30 #include <stdexcept>
31
32 #include <glog/logging.h>
33
34 #include <folly/Likely.h>
35 #include <folly/Portability.h>
36 #include <folly/detail/DiscriminatedPtrDetail.h>
37
38 #if !FOLLY_X64 && !FOLLY_AARCH64 && !FOLLY_PPC64
39 # error "DiscriminatedPtr is x64, arm64 and ppc64 specific code."
40 #endif
41
42 namespace folly {
43
44 /**
45  * Discriminated pointer.
46  *
47  * Given a list of types, a DiscriminatedPtr<Types...> may point to an object
48  * of one of the given types, or may be empty.  DiscriminatedPtr is type-safe:
49  * you may only get a pointer to the type that you put in, otherwise get
50  * throws an exception (and get_nothrow returns nullptr)
51  *
52  * This pointer does not do any kind of lifetime management -- it's not a
53  * "smart" pointer.  You are responsible for deallocating any memory used
54  * to hold pointees, if necessary.
55  */
56 template <typename... Types>
57 class DiscriminatedPtr {
58   // <, not <=, as our indexes are 1-based (0 means "empty")
59   static_assert(sizeof...(Types) < std::numeric_limits<uint16_t>::max(),
60                 "too many types");
61
62  public:
63   /**
64    * Create an empty DiscriminatedPtr.
65    */
66   DiscriminatedPtr() : data_(0) {
67   }
68
69   /**
70    * Create a DiscriminatedPtr that points to an object of type T.
71    * Fails at compile time if T is not a valid type (listed in Types)
72    */
73   template <typename T>
74   explicit DiscriminatedPtr(T* ptr) {
75     set(ptr, typeIndex<T>());
76   }
77
78   /**
79    * Set this DiscriminatedPtr to point to an object of type T.
80    * Fails at compile time if T is not a valid type (listed in Types)
81    */
82   template <typename T>
83   void set(T* ptr) {
84     set(ptr, typeIndex<T>());
85   }
86
87   /**
88    * Get a pointer to the object that this DiscriminatedPtr points to, if it is
89    * of type T.  Fails at compile time if T is not a valid type (listed in
90    * Types), and returns nullptr if this DiscriminatedPtr is empty or points to
91    * an object of a different type.
92    */
93   template <typename T>
94   T* get_nothrow() noexcept {
95     void* p = LIKELY(hasType<T>()) ? ptr() : nullptr;
96     return static_cast<T*>(p);
97   }
98
99   template <typename T>
100   const T* get_nothrow() const noexcept {
101     const void* p = LIKELY(hasType<T>()) ? ptr() : nullptr;
102     return static_cast<const T*>(p);
103   }
104
105   /**
106    * Get a pointer to the object that this DiscriminatedPtr points to, if it is
107    * of type T.  Fails at compile time if T is not a valid type (listed in
108    * Types), and throws std::invalid_argument if this DiscriminatedPtr is empty
109    * or points to an object of a different type.
110    */
111   template <typename T>
112   T* get() {
113     if (UNLIKELY(!hasType<T>())) {
114       throw std::invalid_argument("Invalid type");
115     }
116     return static_cast<T*>(ptr());
117   }
118
119   template <typename T>
120   const T* get() const {
121     if (UNLIKELY(!hasType<T>())) {
122       throw std::invalid_argument("Invalid type");
123     }
124     return static_cast<const T*>(ptr());
125   }
126
127   /**
128    * Return true iff this DiscriminatedPtr is empty.
129    */
130   bool empty() const {
131     return index() == 0;
132   }
133
134   /**
135    * Return true iff the object pointed by this DiscriminatedPtr has type T,
136    * false otherwise.  Fails at compile time if T is not a valid type (listed
137    * in Types...)
138    */
139   template <typename T>
140   bool hasType() const {
141     return index() == typeIndex<T>();
142   }
143
144   /**
145    * Clear this DiscriminatedPtr, making it empty.
146    */
147   void clear() {
148     data_ = 0;
149   }
150
151   /**
152    * Assignment operator from a pointer of type T.
153    */
154   template <typename T>
155   DiscriminatedPtr& operator=(T* ptr) {
156     set(ptr);
157     return *this;
158   }
159
160   /**
161    * Apply a visitor to this object, calling the appropriate overload for
162    * the type currently stored in DiscriminatedPtr.  Throws invalid_argument
163    * if the DiscriminatedPtr is empty.
164    *
165    * The visitor must meet the following requirements:
166    *
167    * - The visitor must allow invocation as a function by overloading
168    *   operator(), unambiguously accepting all values of type T* (or const T*)
169    *   for all T in Types...
170    * - All operations of the function object on T* (or const T*) must
171    *   return the same type (or a static_assert will fire).
172    */
173   template <typename V>
174   typename dptr_detail::VisitorResult<V, Types...>::type apply(V&& visitor) {
175     size_t n = index();
176     if (n == 0) throw std::invalid_argument("Empty DiscriminatedPtr");
177     return dptr_detail::ApplyVisitor<V, Types...>()(
178       n, std::forward<V>(visitor), ptr());
179   }
180
181   template <typename V>
182   typename dptr_detail::ConstVisitorResult<V, Types...>::type apply(V&& visitor)
183   const {
184     size_t n = index();
185     if (n == 0) throw std::invalid_argument("Empty DiscriminatedPtr");
186     return dptr_detail::ApplyConstVisitor<V, Types...>()(
187       n, std::forward<V>(visitor), ptr());
188   }
189
190  private:
191   /**
192    * Get the 1-based type index of T in Types.
193    */
194   template <typename T>
195   uint16_t typeIndex() const {
196     return uint16_t(dptr_detail::GetTypeIndex<T, Types...>::value);
197   }
198
199   uint16_t index() const { return data_ >> 48; }
200   void* ptr() const {
201     return reinterpret_cast<void*>(data_ & ((1ULL << 48) - 1));
202   }
203
204   void set(void* p, uint16_t v) {
205     uintptr_t ip = reinterpret_cast<uintptr_t>(p);
206     CHECK(!(ip >> 48));
207     ip |= static_cast<uintptr_t>(v) << 48;
208     data_ = ip;
209   }
210
211   /**
212    * We store a pointer in the least significant 48 bits of data_, and a type
213    * index (0 = empty, or 1-based index in Types) in the most significant 16
214    * bits.  We rely on the fact that pointers have their most significant 16
215    * bits clear on x86_64.
216    */
217   uintptr_t data_;
218 };
219
220 template <typename Visitor, typename... Args>
221 decltype(auto) apply_visitor(
222     Visitor&& visitor,
223     const DiscriminatedPtr<Args...>& variant) {
224   return variant.apply(std::forward<Visitor>(visitor));
225 }
226
227 template <typename Visitor, typename... Args>
228 decltype(auto) apply_visitor(
229     Visitor&& visitor,
230     DiscriminatedPtr<Args...>& variant) {
231   return variant.apply(std::forward<Visitor>(visitor));
232 }
233
234 template <typename Visitor, typename... Args>
235 decltype(auto) apply_visitor(
236     Visitor&& visitor,
237     DiscriminatedPtr<Args...>&& variant) {
238   return variant.apply(std::forward<Visitor>(visitor));
239 }
240
241 } // namespace folly