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