blob: d875e3b54c1cab51cb69f662286c3af51c1c9605 [file] [log] [blame]
Alex Vakulenkob6513a12014-05-05 17:23:40 -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 buffet::Any class.
6
7#ifndef BUFFET_ANY_INTERNAL_IMPL_H_
8#define BUFFET_ANY_INTERNAL_IMPL_H_
9
10#include <type_traits>
11#include <typeinfo>
12#include <utility>
13
14#include <base/logging.h>
15
16namespace buffet {
17
18namespace internal_details {
19
20// An extension to std::is_convertible to allow conversion from an enum to
21// an integral type which std::is_convertible does not indicate as supported.
22template <typename From, typename To>
23struct IsConvertible : public std::integral_constant<bool,
24 std::is_convertible<From, To>::value ||
25 (std::is_enum<From>::value && std::is_integral<To>::value)> {
26};
27// TryConvert is a helper function that does a safe compile-time conditional
28// type cast between data types that may not be always convertible.
29// From and To are the source and destination types.
30// The function returns true if conversion was possible/successful.
31template <typename From, typename To>
32inline typename std::enable_if<IsConvertible<From, To>::value, bool>::type
33TryConvert(const From& in, To* out) {
34 *out = static_cast<To>(in);
35 return true;
36}
37template <typename From, typename To>
38inline typename std::enable_if<!IsConvertible<From, To>::value, bool>::type
39TryConvert(const From& in, To* out) {
40 return false;
41}
42
Alex Vakulenko970d8342014-08-13 10:16:42 -070043class Buffer; // Forward declaration of data buffer container.
Alex Vakulenkob6513a12014-05-05 17:23:40 -070044
45// Abstract base class for contained variant data.
46struct Data {
47 virtual ~Data() {}
48 // Returns the type information for the contained data.
49 virtual const std::type_info& GetType() const = 0;
50 // Copies the contained data to the output |buffer|.
51 virtual void CopyTo(Buffer* buffer) const = 0;
52 // Checks if the contained data is an integer type (not necessarily an 'int').
53 virtual bool IsConvertibleToInteger() const = 0;
54 // Gets the contained integral value as an integer.
55 virtual intmax_t GetAsInteger() const = 0;
56};
57
58// Concrete implementation of variant data of type T.
59template<typename T>
60struct TypedData : public Data {
61 explicit TypedData(const T& value) : value_(value) {}
62
Alex Vakulenko5a9e7182014-08-11 15:59:58 -070063 const std::type_info& GetType() const override { return typeid(T); }
64 void CopyTo(Buffer* buffer) const override;
65 bool IsConvertibleToInteger() const override {
Alex Vakulenkob6513a12014-05-05 17:23:40 -070066 return std::is_integral<T>::value || std::is_enum<T>::value;
67 }
Alex Vakulenko5a9e7182014-08-11 15:59:58 -070068 intmax_t GetAsInteger() const override {
Alex Vakulenkob6513a12014-05-05 17:23:40 -070069 intmax_t int_val = 0;
70 bool converted = TryConvert(value_, &int_val);
71 CHECK(converted) << "Unable to convert value of type " << typeid(T).name()
72 << " to integer";
73 return int_val;
74 }
75 // Special method to copy data of the same type
76 // without reallocating the buffer.
77 void FastAssign(const T& source) { value_ = source; }
78
79 T value_;
80};
81
82// Buffer class that stores the contained variant data.
83// To improve performance and reduce memory fragmentation, small variants
84// are stored in pre-allocated memory buffers that are part of the Any class.
85// If the memory requirements are larger than the set limit or the type is
86// non-trivially copyable, then the contained class is allocated in a separate
87// memory block and the pointer to that memory is contained within this memory
88// buffer class.
89class Buffer {
90 public:
91 enum StorageType { kExternal, kContained };
92 Buffer() : external_ptr_(nullptr), storage_(kExternal) {}
93 ~Buffer() {
94 Clear();
95 }
96
97 Buffer(const Buffer& rhs) : Buffer() {
98 rhs.CopyTo(this);
99 }
100 Buffer& operator=(const Buffer& rhs) {
101 rhs.CopyTo(this);
102 return *this;
103 }
104
105 // Returns the underlying pointer to contained data. Uses either the pointer
106 // or the raw data depending on |storage_| type.
107 inline Data* GetDataPtr() {
108 return (storage_ == kExternal) ?
109 external_ptr_ : reinterpret_cast<Data*>(contained_buffer_);
110 }
111 inline const Data* GetDataPtr() const {
112 return (storage_ == kExternal) ?
113 external_ptr_ : reinterpret_cast<const Data*>(contained_buffer_);
114 }
115
116 // Destroys the contained object (and frees memory if needed).
117 void Clear() {
118 Data* data = GetDataPtr();
119 if (storage_ == kExternal) {
120 delete data;
121 } else {
122 // Call the destructor manually, since the object was constructed inline
123 // in the pre-allocated buffer. We still need to call the destructor
124 // to free any associated resources, but we can't call delete |data| here.
125 data->~Data();
126 }
127 external_ptr_ = nullptr;
128 storage_ = kExternal;
129 }
130
131 // Stores a value of type T.
132 template<typename T>
133 void Assign(T value) {
134 using Type = typename std::decay<T>::type;
135 using DataType = TypedData<Type>;
136 Data* ptr = GetDataPtr();
137 if (ptr && ptr->GetType() == typeid(Type)) {
138 // We assign the data to the variant container, which already
139 // has the data of the same type. Do fast copy with no memory
140 // reallocation.
141 DataType* typed_ptr = static_cast<DataType*>(ptr);
142 typed_ptr->FastAssign(value);
143 } else {
144 Clear();
145 // TODO(avakulenko): [see crbug.com/379833]
146 // Unfortunately, GCC doesn't support std::is_trivially_copyable<T> yet,
147 // so using std::is_trivial instead, which is a bit more restrictive.
148 // Once GCC has support for is_trivially_copyable, update the following.
149 if (!std::is_trivial<Type>::value ||
150 sizeof(DataType) > sizeof(contained_buffer_)) {
151 // If it is too big or not trivially copyable, allocate it separately.
152 external_ptr_ = new DataType(value);
153 storage_ = kExternal;
154 } else {
155 // Otherwise just use the pre-allocated buffer.
156 DataType* address = reinterpret_cast<DataType*>(contained_buffer_);
157 // Make sure we still call the copy constructor.
158 // Call the constructor manually by using placement 'new'.
159 new (address) DataType(value);
160 storage_ = kContained;
161 }
162 }
163 }
164
165 // Helper methods to retrieve a reference to contained data.
166 // These assume that type checking has already been performed by Any
167 // so the type cast is valid and will succeed.
168 template<typename T>
169 const T& GetData() const {
170 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
171 return static_cast<const DataType*>(GetDataPtr())->value_;
172 }
173 template<typename T>
174 T& GetData() {
175 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
176 return static_cast<DataType*>(GetDataPtr())->value_;
177 }
178
179 // Returns true if the buffer has no contained data.
180 bool IsEmpty() const {
181 return (storage_ == kExternal && external_ptr_ == nullptr);
182 }
183
184 // Copies the data from the current buffer into the |destination|.
185 void CopyTo(Buffer* destination) const {
186 if (IsEmpty()) {
187 destination->Clear();
188 } else {
189 GetDataPtr()->CopyTo(destination);
190 }
191 }
192
193 union {
194 // |external_ptr_| is a pointer to a larger object allocated in
195 // a separate memory block.
196 Data* external_ptr_;
197 // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects.
198 // Pre-allocate enough memory to store objects as big as "double".
199 unsigned char contained_buffer_[sizeof(TypedData<double>)];
200 };
201 // Depending on a value of |storage_|, either |external_ptr_| or
202 // |contained_buffer_| above is used to get a pointer to memory containing
203 // the variant data.
204 StorageType storage_; // Declare after the union to eliminate member padding.
205};
206
207template<typename T>
208void TypedData<T>::CopyTo(Buffer* buffer) const { buffer->Assign(value_); }
209
210} // namespace internal_details
211
212} // namespace buffet
213
214#endif // BUFFET_ANY_INTERNAL_IMPL_H_
215