1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
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(
60       sizeof...(Types) < std::numeric_limits<uint16_t>::max(),
61       "too many types");
62 
63  public:
64   /**
65    * Create an empty DiscriminatedPtr.
66    */
DiscriminatedPtr()67   DiscriminatedPtr() : data_(0) {}
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>
DiscriminatedPtr(T * ptr)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>
set(T * ptr)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>
get_nothrow()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>
get_nothrow()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>
get()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>
get()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    */
empty()130   bool empty() const { return index() == 0; }
131 
132   /**
133    * Return true iff the object pointed by this DiscriminatedPtr has type T,
134    * false otherwise.  Fails at compile time if T is not a valid type (listed
135    * in Types...)
136    */
137   template <typename T>
hasType()138   bool hasType() const {
139     return index() == typeIndex<T>();
140   }
141 
142   /**
143    * Clear this DiscriminatedPtr, making it empty.
144    */
clear()145   void clear() { data_ = 0; }
146 
147   /**
148    * Assignment operator from a pointer of type T.
149    */
150   template <typename T>
151   DiscriminatedPtr& operator=(T* ptr) {
152     set(ptr);
153     return *this;
154   }
155 
156   /**
157    * Apply a visitor to this object, calling the appropriate overload for
158    * the type currently stored in DiscriminatedPtr.  Throws invalid_argument
159    * if the DiscriminatedPtr is empty.
160    *
161    * The visitor must meet the following requirements:
162    *
163    * - The visitor must allow invocation as a function by overloading
164    *   operator(), unambiguously accepting all values of type T* (or const T*)
165    *   for all T in Types...
166    * - All operations of the function object on T* (or const T*) must
167    *   return the same type (or a static_assert will fire).
168    */
169   template <typename V>
apply(V && visitor)170   typename dptr_detail::VisitorResult<V, Types...>::type apply(V&& visitor) {
171     size_t n = index();
172     if (n == 0) {
173       throw std::invalid_argument("Empty DiscriminatedPtr");
174     }
175     return dptr_detail::ApplyVisitor<V, Types...>()(
176         n, std::forward<V>(visitor), ptr());
177   }
178 
179   template <typename V>
apply(V && visitor)180   typename dptr_detail::ConstVisitorResult<V, Types...>::type apply(
181       V&& visitor) const {
182     size_t n = index();
183     if (n == 0) {
184       throw std::invalid_argument("Empty DiscriminatedPtr");
185     }
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>
typeIndex()195   uint16_t typeIndex() const {
196     return uint16_t(dptr_detail::GetTypeIndex<T, Types...>::value);
197   }
198 
index()199   uint16_t index() const { return data_ >> 48; }
ptr()200   void* ptr() const {
201     return reinterpret_cast<void*>(data_ & ((1ULL << 48) - 1));
202   }
203 
set(void * p,uint16_t v)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>
decltype(auto)221 decltype(auto) apply_visitor(
222     Visitor&& visitor, const DiscriminatedPtr<Args...>& variant) {
223   return variant.apply(std::forward<Visitor>(visitor));
224 }
225 
226 template <typename Visitor, typename... Args>
decltype(auto)227 decltype(auto) apply_visitor(
228     Visitor&& visitor, DiscriminatedPtr<Args...>& variant) {
229   return variant.apply(std::forward<Visitor>(visitor));
230 }
231 
232 template <typename Visitor, typename... Args>
decltype(auto)233 decltype(auto) apply_visitor(
234     Visitor&& visitor, DiscriminatedPtr<Args...>&& variant) {
235   return variant.apply(std::forward<Visitor>(visitor));
236 }
237 
238 } // namespace folly
239