folly/wangle -> wangle cutover
[folly.git] / folly / DiscriminatedPtr.h
1 /*
2  * Copyright 2015 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 #ifndef FOLLY_DISCRIMINATEDPTR_H_
28 #define FOLLY_DISCRIMINATEDPTR_H_
29
30 #include <limits>
31 #include <stdexcept>
32 #include <glog/logging.h>
33 #include <folly/Likely.h>
34 #include <folly/Portability.h>
35 #include <folly/detail/DiscriminatedPtrDetail.h>
36
37 #if !FOLLY_X64
38 # error "DiscriminatedPtr is x64-specific code."
39 #endif
40
41 namespace folly {
42
43 /**
44  * Discriminated pointer.
45  *
46  * Given a list of types, a DiscriminatedPtr<Types...> may point to an object
47  * of one of the given types, or may be empty.  DiscriminatedPtr is type-safe:
48  * you may only get a pointer to the type that you put in, otherwise get
49  * throws an exception (and get_nothrow returns nullptr)
50  *
51  * This pointer does not do any kind of lifetime management -- it's not a
52  * "smart" pointer.  You are responsible for deallocating any memory used
53  * to hold pointees, if necessary.
54  */
55 template <typename... Types>
56 class DiscriminatedPtr {
57   // <, not <=, as our indexes are 1-based (0 means "empty")
58   static_assert(sizeof...(Types) < std::numeric_limits<uint16_t>::max(),
59                 "too many types");
60
61  public:
62   /**
63    * Create an empty DiscriminatedPtr.
64    */
65   DiscriminatedPtr() : data_(0) {
66   }
67
68   /**
69    * Create a DiscriminatedPtr that points to an object of type T.
70    * Fails at compile time if T is not a valid type (listed in Types)
71    */
72   template <typename T>
73   explicit DiscriminatedPtr(T* ptr) {
74     set(ptr, typeIndex<T>());
75   }
76
77   /**
78    * Set this DiscriminatedPtr to point to an object of type T.
79    * Fails at compile time if T is not a valid type (listed in Types)
80    */
81   template <typename T>
82   void set(T* ptr) {
83     set(ptr, typeIndex<T>());
84   }
85
86   /**
87    * Get a pointer to the object that this DiscriminatedPtr points to, if it is
88    * of type T.  Fails at compile time if T is not a valid type (listed in
89    * Types), and returns nullptr if this DiscriminatedPtr is empty or points to
90    * an object of a different type.
91    */
92   template <typename T>
93   T* get_nothrow() noexcept {
94     void* p = LIKELY(hasType<T>()) ? ptr() : nullptr;
95     return static_cast<T*>(p);
96   }
97
98   template <typename T>
99   const T* get_nothrow() const noexcept {
100     const void* p = LIKELY(hasType<T>()) ? ptr() : nullptr;
101     return static_cast<const T*>(p);
102   }
103
104   /**
105    * Get a pointer to the object that this DiscriminatedPtr points to, if it is
106    * of type T.  Fails at compile time if T is not a valid type (listed in
107    * Types), and throws std::invalid_argument if this DiscriminatedPtr is empty
108    * or points to an object of a different type.
109    */
110   template <typename T>
111   T* get() {
112     if (UNLIKELY(!hasType<T>())) {
113       throw std::invalid_argument("Invalid type");
114     }
115     return static_cast<T*>(ptr());
116   }
117
118   template <typename T>
119   const T* get() const {
120     if (UNLIKELY(!hasType<T>())) {
121       throw std::invalid_argument("Invalid type");
122     }
123     return static_cast<const T*>(ptr());
124   }
125
126   /**
127    * Return true iff this DiscriminatedPtr is empty.
128    */
129   bool empty() const {
130     return index() == 0;
131   }
132
133   /**
134    * Return true iff the object pointed by this DiscriminatedPtr has type T,
135    * false otherwise.  Fails at compile time if T is not a valid type (listed
136    * in Types...)
137    */
138   template <typename T>
139   bool hasType() const {
140     return index() == typeIndex<T>();
141   }
142
143   /**
144    * Clear this DiscriminatedPtr, making it empty.
145    */
146   void clear() {
147     data_ = 0;
148   }
149
150   /**
151    * Assignment operator from a pointer of type T.
152    */
153   template <typename T>
154   DiscriminatedPtr& operator=(T* ptr) {
155     set(ptr);
156     return *this;
157   }
158
159   /**
160    * Apply a visitor to this object, calling the appropriate overload for
161    * the type currently stored in DiscriminatedPtr.  Throws invalid_argument
162    * if the DiscriminatedPtr is empty.
163    *
164    * The visitor must meet the following requirements:
165    *
166    * - The visitor must allow invocation as a function by overloading
167    *   operator(), unambiguously accepting all values of type T* (or const T*)
168    *   for all T in Types...
169    * - All operations of the function object on T* (or const T*) must
170    *   return the same type (or a static_assert will fire).
171    */
172   template <typename V>
173   typename dptr_detail::VisitorResult<V, Types...>::type apply(V&& visitor) {
174     size_t n = index();
175     if (n == 0) throw std::invalid_argument("Empty DiscriminatedPtr");
176     return dptr_detail::ApplyVisitor<V, Types...>()(
177       n, std::forward<V>(visitor), ptr());
178   }
179
180   template <typename V>
181   typename dptr_detail::ConstVisitorResult<V, Types...>::type apply(V&& visitor)
182   const {
183     size_t n = index();
184     if (n == 0) throw std::invalid_argument("Empty DiscriminatedPtr");
185     return dptr_detail::ApplyConstVisitor<V, Types...>()(
186       n, std::forward<V>(visitor), ptr());
187   }
188
189  private:
190   /**
191    * Get the 1-based type index of T in Types.
192    */
193   template <typename T>
194   size_t typeIndex() const {
195     return dptr_detail::GetTypeIndex<T, Types...>::value;
196   }
197
198   uint16_t index() const { return data_ >> 48; }
199   void* ptr() const {
200     return reinterpret_cast<void*>(data_ & ((1ULL << 48) - 1));
201   }
202
203   void set(void* p, uint16_t v) {
204     uintptr_t ip = reinterpret_cast<uintptr_t>(p);
205     CHECK(!(ip >> 48));
206     ip |= static_cast<uintptr_t>(v) << 48;
207     data_ = ip;
208   }
209
210   /**
211    * We store a pointer in the least significant 48 bits of data_, and a type
212    * index (0 = empty, or 1-based index in Types) in the most significant 16
213    * bits.  We rely on the fact that pointers have their most significant 16
214    * bits clear on x86_64.
215    */
216   uintptr_t data_;
217 };
218
219 }  // namespace folly
220
221 #endif /* FOLLY_DISCRIMINATEDPTR_H_ */