add makeSystemError*() helper functions
[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) {
177       throw std::invalid_argument("Empty DiscriminatedPtr");
178     }
179     return dptr_detail::ApplyVisitor<V, Types...>()(
180       n, std::forward<V>(visitor), ptr());
181   }
182
183   template <typename V>
184   typename dptr_detail::ConstVisitorResult<V, Types...>::type apply(V&& visitor)
185   const {
186     size_t n = index();
187     if (n == 0) {
188       throw std::invalid_argument("Empty DiscriminatedPtr");
189     }
190     return dptr_detail::ApplyConstVisitor<V, Types...>()(
191       n, std::forward<V>(visitor), ptr());
192   }
193
194  private:
195   /**
196    * Get the 1-based type index of T in Types.
197    */
198   template <typename T>
199   uint16_t typeIndex() const {
200     return uint16_t(dptr_detail::GetTypeIndex<T, Types...>::value);
201   }
202
203   uint16_t index() const { return data_ >> 48; }
204   void* ptr() const {
205     return reinterpret_cast<void*>(data_ & ((1ULL << 48) - 1));
206   }
207
208   void set(void* p, uint16_t v) {
209     uintptr_t ip = reinterpret_cast<uintptr_t>(p);
210     CHECK(!(ip >> 48));
211     ip |= static_cast<uintptr_t>(v) << 48;
212     data_ = ip;
213   }
214
215   /**
216    * We store a pointer in the least significant 48 bits of data_, and a type
217    * index (0 = empty, or 1-based index in Types) in the most significant 16
218    * bits.  We rely on the fact that pointers have their most significant 16
219    * bits clear on x86_64.
220    */
221   uintptr_t data_;
222 };
223
224 template <typename Visitor, typename... Args>
225 decltype(auto) apply_visitor(
226     Visitor&& visitor,
227     const DiscriminatedPtr<Args...>& variant) {
228   return variant.apply(std::forward<Visitor>(visitor));
229 }
230
231 template <typename Visitor, typename... Args>
232 decltype(auto) apply_visitor(
233     Visitor&& visitor,
234     DiscriminatedPtr<Args...>& variant) {
235   return variant.apply(std::forward<Visitor>(visitor));
236 }
237
238 template <typename Visitor, typename... Args>
239 decltype(auto) apply_visitor(
240     Visitor&& visitor,
241     DiscriminatedPtr<Args...>&& variant) {
242   return variant.apply(std::forward<Visitor>(visitor));
243 }
244
245 } // namespace folly