blob: 13aef0363dbc7593b0dabf1b6998daffa197db7e [file] [log] [blame]
Alex Vakulenko9205c772014-08-22 15:05:35 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Internal implementation of chromeos::Any class.
6
7#ifndef LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_
8#define LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_
9
10#include <type_traits>
11#include <typeinfo>
12#include <utility>
13
14#include <base/logging.h>
Alex Vakulenkob3813652014-08-27 08:04:22 -070015#include <chromeos/dbus/data_serialization.h>
Alex Vakulenkobba50fa2014-10-03 08:37:54 -070016#include <chromeos/type_name_undecorate.h>
Alex Vakulenko9205c772014-08-22 15:05:35 -070017
18namespace chromeos {
19
20namespace internal_details {
21
22// An extension to std::is_convertible to allow conversion from an enum to
23// an integral type which std::is_convertible does not indicate as supported.
24template <typename From, typename To>
25struct IsConvertible : public std::integral_constant<bool,
26 std::is_convertible<From, To>::value ||
27 (std::is_enum<From>::value && std::is_integral<To>::value)> {
28};
29// TryConvert is a helper function that does a safe compile-time conditional
30// type cast between data types that may not be always convertible.
31// From and To are the source and destination types.
32// The function returns true if conversion was possible/successful.
33template <typename From, typename To>
34inline typename std::enable_if<IsConvertible<From, To>::value, bool>::type
35TryConvert(const From& in, To* out) {
36 *out = static_cast<To>(in);
37 return true;
38}
39template <typename From, typename To>
40inline typename std::enable_if<!IsConvertible<From, To>::value, bool>::type
41TryConvert(const From& in, To* out) {
42 return false;
43}
44
Alex Vakulenko88c1b4b2014-09-03 15:16:10 -070045//////////////////////////////////////////////////////////////////////////////
46// Provide a way to compare values of unspecified types without compiler errors
47// when no operator==() is provided for a given type. This is important to
48// allow Any class to have operator==(), yet still allowing arbitrary types
49// (not necessarily comparable) to be placed inside Any without resulting in
50// compile-time error.
51//
52// We achieve this in two ways. First, we provide a IsEqualityComparable<T>
53// class that can be used in compile-time conditions to determine if there is
54// operator==() defined that takes values of type T (or which can be implicitly
55// converted to type T). Secondly, this allows us to specialize a helper
56// compare function EqCompare<T>(v1, v2) to use operator==() for types that
57// are comparable, and just return false for those that are not.
58//
59// IsEqualityComparableHelper<T> is a helper class for implementing an
60// an STL-compatible IsEqualityComparable<T> containing a Boolean member |value|
61// which evaluates to true for comparable types and false otherwise.
62template<typename T>
63struct IsEqualityComparableHelper {
64 struct IntWrapper {
65 // A special structure that provides a constructor that takes an int.
66 // This way, an int argument passed to a function will be favored over
67 // IntWrapper when both overloads are provided.
68 // Also this constructor must NOT be explicit.
69 // NOLINTNEXTLINE(runtime/explicit)
70 IntWrapper(int dummy) {} // do nothing
71 };
72
73 // Here is an obscure trick to determine if a type U has operator==().
74 // We are providing two function prototypes for TriggerFunction. One that
75 // takes an argument of type IntWrapper (which is implicitly convertible from
76 // an int), and returns an std::false_type. This is a fall-back mechanism.
77 template<typename U>
78 static std::false_type TriggerFunction(IntWrapper dummy);
79
80 // The second overload of TriggerFunction takes an int (explicitly) and
81 // returns std::true_type. If both overloads are available, this one will be
82 // chosen when referencing it as TriggerFunction(0), since it is a better
83 // (more specific) match.
84 //
85 // However this overload is available only for types that support operator==.
86 // This is achieved by employing SFINAE mechanism inside a template function
87 // overload that refers to operator==() for two values of types U&. This is
88 // used inside decltype(), so no actual code is executed. If the types
89 // are not comparable, reference to "==" would fail and the compiler will
90 // simply ignore this overload due to SFIANE.
91 //
92 // The final little trick used here is the reliance on operator comma inside
93 // the decltype() expression. The result of the expression is always
94 // std::true_type(). The expression on the left of comma is just evaluated and
95 // discarded. If it evaluates successfully (i.e. the type has operator==), the
96 // return value of the function is set to be std::true_value. If it fails,
97 // the whole function prototype is discarded and is not available in the
98 // IsEqualityComparableHelper<T> class.
99 //
100 // Here we use std::declval<U&>() to make sure we have operator==() that takes
101 // lvalue references to type U which is not necessarily default-constructible.
102 template<typename U>
103 static decltype((std::declval<U&>() == std::declval<U&>()), std::true_type())
104 TriggerFunction(int dummy);
105
106 // Finally, use the return type of the overload of TriggerFunction that
107 // matches the argument (int) to be aliased to type |type|. If T is
108 // comparable, there will be two overloads and the more specific (int) will
109 // be chosen which returns std::true_value. If the type is non-comparable,
110 // there will be only one version of TriggerFunction available which
111 // returns std::false_value.
112 using type = decltype(TriggerFunction<T>(0));
113};
114
115// IsEqualityComparable<T> is simply a class that derives from either
116// std::true_value, if type T is comparable, or from std::false_value, if the
117// type is non-comparable. We just use |type| alias from
118// IsEqualityComparableHelper<T> as the base class.
119template<typename T>
120struct IsEqualityComparable : IsEqualityComparableHelper<T>::type {
121};
122
123// EqCompare() overload for non-comparable types. Always returns false.
124template<typename T>
125inline typename std::enable_if<!IsEqualityComparable<T>::value, bool>::type
126 EqCompare(const T& v1, const T& v2) { return false; }
127
128// EqCompare overload for comparable types. Calls operator==(v1, v2) to compare.
129template<typename T>
130inline typename std::enable_if<IsEqualityComparable<T>::value, bool>::type
131 EqCompare(const T& v1, const T& v2) { return (v1 == v2); }
132
133//////////////////////////////////////////////////////////////////////////////
134
Alex Vakulenko9205c772014-08-22 15:05:35 -0700135class Buffer; // Forward declaration of data buffer container.
136
137// Abstract base class for contained variant data.
138struct Data {
139 virtual ~Data() {}
140 // Returns the type information for the contained data.
141 virtual const std::type_info& GetType() const = 0;
142 // Copies the contained data to the output |buffer|.
143 virtual void CopyTo(Buffer* buffer) const = 0;
144 // Moves the contained data to the output |buffer|.
145 virtual void MoveTo(Buffer* buffer) = 0;
146 // Checks if the contained data is an integer type (not necessarily an 'int').
147 virtual bool IsConvertibleToInteger() const = 0;
148 // Gets the contained integral value as an integer.
149 virtual intmax_t GetAsInteger() const = 0;
Alex Vakulenko7c294a22014-08-23 19:31:27 -0700150 // Writes the contained value to the D-Bus message buffer.
Alex Vakulenko003e3bb2014-11-09 13:21:59 -0800151 virtual void AppendToDBusMessage(dbus::MessageWriter* writer) const = 0;
Alex Vakulenko88c1b4b2014-09-03 15:16:10 -0700152 // Compares if the two data containers have objects of the same value.
153 virtual bool CompareEqual(const Data* other_data) const = 0;
Alex Vakulenko9205c772014-08-22 15:05:35 -0700154};
155
156// Concrete implementation of variant data of type T.
157template<typename T>
158struct TypedData : public Data {
159 explicit TypedData(const T& value) : value_(value) {}
160 // NOLINTNEXTLINE(build/c++11)
161 explicit TypedData(T&& value) : value_(std::move(value)) {}
162
163 const std::type_info& GetType() const override { return typeid(T); }
164 void CopyTo(Buffer* buffer) const override;
165 void MoveTo(Buffer* buffer) override;
166 bool IsConvertibleToInteger() const override {
167 return std::is_integral<T>::value || std::is_enum<T>::value;
168 }
169 intmax_t GetAsInteger() const override {
170 intmax_t int_val = 0;
171 bool converted = TryConvert(value_, &int_val);
Alex Vakulenkobba50fa2014-10-03 08:37:54 -0700172 CHECK(converted) << "Unable to convert value of type '"
173 << GetUndecoratedTypeName<T>()
174 << "' to integer";
Alex Vakulenko9205c772014-08-22 15:05:35 -0700175 return int_val;
176 }
Alex Vakulenko003e3bb2014-11-09 13:21:59 -0800177
178 template<typename U>
179 static typename std::enable_if<dbus_utils::IsTypeSupported<U>::value>::type
180 AppendValueHelper(dbus::MessageWriter* writer, const U& value) {
181 chromeos::dbus_utils::AppendValueToWriterAsVariant(writer, value);
182 }
183 template<typename U>
184 static typename std::enable_if<!dbus_utils::IsTypeSupported<U>::value>::type
185 AppendValueHelper(dbus::MessageWriter* writer, const U& value) {
186 LOG(FATAL) << "Type '" << GetUndecoratedTypeName<U>()
187 << "' is not supported by D-Bus";
188 }
189
190 void AppendToDBusMessage(dbus::MessageWriter* writer) const override {
191 return AppendValueHelper(writer, value_);
Alex Vakulenko7c294a22014-08-23 19:31:27 -0700192 }
Alex Vakulenko88c1b4b2014-09-03 15:16:10 -0700193
194 bool CompareEqual(const Data* other_data) const override {
195 return EqCompare<T>(value_,
196 static_cast<const TypedData<T>*>(other_data)->value_);
197 }
198
Alex Vakulenko9205c772014-08-22 15:05:35 -0700199 // Special methods to copy/move data of the same type
200 // without reallocating the buffer.
201 void FastAssign(const T& source) { value_ = source; }
202 // NOLINTNEXTLINE(build/c++11)
203 void FastAssign(T&& source) { value_ = std::move(source); }
204
205 T value_;
206};
207
208// Buffer class that stores the contained variant data.
209// To improve performance and reduce memory fragmentation, small variants
210// are stored in pre-allocated memory buffers that are part of the Any class.
211// If the memory requirements are larger than the set limit or the type is
212// non-trivially copyable, then the contained class is allocated in a separate
213// memory block and the pointer to that memory is contained within this memory
214// buffer class.
215class Buffer {
216 public:
217 enum StorageType { kExternal, kContained };
218 Buffer() : external_ptr_(nullptr), storage_(kExternal) {}
219 ~Buffer() {
220 Clear();
221 }
222
223 Buffer(const Buffer& rhs) : Buffer() {
224 rhs.CopyTo(this);
225 }
226 // NOLINTNEXTLINE(build/c++11)
227 Buffer(Buffer&& rhs) : Buffer() {
228 rhs.MoveTo(this);
229 }
230 Buffer& operator=(const Buffer& rhs) {
231 rhs.CopyTo(this);
232 return *this;
233 }
234 // NOLINTNEXTLINE(build/c++11)
235 Buffer& operator=(Buffer&& rhs) {
236 rhs.MoveTo(this);
237 return *this;
238 }
239
240 // Returns the underlying pointer to contained data. Uses either the pointer
241 // or the raw data depending on |storage_| type.
242 inline Data* GetDataPtr() {
243 return (storage_ == kExternal) ?
244 external_ptr_ : reinterpret_cast<Data*>(contained_buffer_);
245 }
246 inline const Data* GetDataPtr() const {
247 return (storage_ == kExternal) ?
248 external_ptr_ : reinterpret_cast<const Data*>(contained_buffer_);
249 }
250
251 // Destroys the contained object (and frees memory if needed).
252 void Clear() {
253 Data* data = GetDataPtr();
254 if (storage_ == kExternal) {
255 delete data;
256 } else {
257 // Call the destructor manually, since the object was constructed inline
258 // in the pre-allocated buffer. We still need to call the destructor
259 // to free any associated resources, but we can't call delete |data| here.
260 data->~Data();
261 }
262 external_ptr_ = nullptr;
263 storage_ = kExternal;
264 }
265
266 // Stores a value of type T.
267 template<typename T>
268 void Assign(T&& value) { // NOLINT(build/c++11)
269 using Type = typename std::decay<T>::type;
270 using DataType = TypedData<Type>;
271 Data* ptr = GetDataPtr();
272 if (ptr && ptr->GetType() == typeid(Type)) {
273 // We assign the data to the variant container, which already
274 // has the data of the same type. Do fast copy/move with no memory
275 // reallocation.
276 DataType* typed_ptr = static_cast<DataType*>(ptr);
277 // NOLINTNEXTLINE(build/c++11)
278 typed_ptr->FastAssign(std::forward<T>(value));
279 } else {
280 Clear();
281 // TODO(avakulenko): [see crbug.com/379833]
282 // Unfortunately, GCC doesn't support std::is_trivially_copyable<T> yet,
283 // so using std::is_trivial instead, which is a bit more restrictive.
284 // Once GCC has support for is_trivially_copyable, update the following.
285 if (!std::is_trivial<Type>::value ||
286 sizeof(DataType) > sizeof(contained_buffer_)) {
287 // If it is too big or not trivially copyable, allocate it separately.
288 // NOLINTNEXTLINE(build/c++11)
289 external_ptr_ = new DataType(std::forward<T>(value));
290 storage_ = kExternal;
291 } else {
292 // Otherwise just use the pre-allocated buffer.
293 DataType* address = reinterpret_cast<DataType*>(contained_buffer_);
294 // Make sure we still call the copy/move constructor.
295 // Call the constructor manually by using placement 'new'.
296 // NOLINTNEXTLINE(build/c++11)
297 new (address) DataType(std::forward<T>(value));
298 storage_ = kContained;
299 }
300 }
301 }
302
303 // Helper methods to retrieve a reference to contained data.
304 // These assume that type checking has already been performed by Any
305 // so the type cast is valid and will succeed.
306 template<typename T>
307 const T& GetData() const {
308 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
309 return static_cast<const DataType*>(GetDataPtr())->value_;
310 }
311 template<typename T>
312 T& GetData() {
313 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
314 return static_cast<DataType*>(GetDataPtr())->value_;
315 }
316
317 // Returns true if the buffer has no contained data.
318 bool IsEmpty() const {
319 return (storage_ == kExternal && external_ptr_ == nullptr);
320 }
321
322 // Copies the data from the current buffer into the |destination|.
323 void CopyTo(Buffer* destination) const {
324 if (IsEmpty()) {
325 destination->Clear();
326 } else {
327 GetDataPtr()->CopyTo(destination);
328 }
329 }
330
331 // Moves the data from the current buffer into the |destination|.
332 void MoveTo(Buffer* destination) {
333 if (IsEmpty()) {
334 destination->Clear();
335 } else {
336 if (storage_ == kExternal) {
337 destination->Clear();
338 destination->storage_ = kExternal;
339 destination->external_ptr_ = external_ptr_;
340 external_ptr_ = nullptr;
341 } else {
342 GetDataPtr()->MoveTo(destination);
343 }
344 }
345 }
346
347 union {
348 // |external_ptr_| is a pointer to a larger object allocated in
349 // a separate memory block.
350 Data* external_ptr_;
351 // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects.
352 // Pre-allocate enough memory to store objects as big as "double".
353 unsigned char contained_buffer_[sizeof(TypedData<double>)];
354 };
355 // Depending on a value of |storage_|, either |external_ptr_| or
356 // |contained_buffer_| above is used to get a pointer to memory containing
357 // the variant data.
358 StorageType storage_; // Declare after the union to eliminate member padding.
359};
360
361template<typename T>
362void TypedData<T>::CopyTo(Buffer* buffer) const { buffer->Assign(value_); }
363template<typename T>
364void TypedData<T>::MoveTo(Buffer* buffer) { buffer->Assign(std::move(value_)); }
365
366} // namespace internal_details
367
368} // namespace chromeos
369
370#endif // LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_
371