1/*
2 * Copyright 2011-present 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
42namespace 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 */
56template <typename... Types>
57class 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 */
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>
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(
185 V&& visitor) 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 {
204 return data_ >> 48;
205 }
206 void* ptr() const {
207 return reinterpret_cast<void*>(data_ & ((1ULL << 48) - 1));
208 }
209
210 void set(void* p, uint16_t v) {
211 uintptr_t ip = reinterpret_cast<uintptr_t>(p);
212 CHECK(!(ip >> 48));
213 ip |= static_cast<uintptr_t>(v) << 48;
214 data_ = ip;
215 }
216
217 /**
218 * We store a pointer in the least significant 48 bits of data_, and a type
219 * index (0 = empty, or 1-based index in Types) in the most significant 16
220 * bits. We rely on the fact that pointers have their most significant 16
221 * bits clear on x86_64.
222 */
223 uintptr_t data_;
224};
225
226template <typename Visitor, typename... Args>
227decltype(auto) apply_visitor(
228 Visitor&& visitor,
229 const DiscriminatedPtr<Args...>& variant) {
230 return variant.apply(std::forward<Visitor>(visitor));
231}
232
233template <typename Visitor, typename... Args>
234decltype(auto) apply_visitor(
235 Visitor&& visitor,
236 DiscriminatedPtr<Args...>& variant) {
237 return variant.apply(std::forward<Visitor>(visitor));
238}
239
240template <typename Visitor, typename... Args>
241decltype(auto) apply_visitor(
242 Visitor&& visitor,
243 DiscriminatedPtr<Args...>&& variant) {
244 return variant.apply(std::forward<Visitor>(visitor));
245}
246
247} // namespace folly
248