Further improve performance by fixing specialized data accessors
[cppcodec.git] / cppcodec / data / access.hpp
1 /**
2  *  Copyright (C) 2015 Topology LP
3  *  All rights reserved.
4  *
5  *  Permission is hereby granted, free of charge, to any person obtaining a copy
6  *  of this software and associated documentation files (the "Software"), to
7  *  deal in the Software without restriction, including without limitation the
8  *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9  *  sell copies of the Software, and to permit persons to whom the Software is
10  *  furnished to do so, subject to the following conditions:
11  *
12  *  The above copyright notice and this permission notice shall be included in
13  *  all copies or substantial portions of the Software.
14  *
15  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  *  IN THE SOFTWARE.
22  */
23
24 #ifndef CPPCODEC_DETAIL_DATA_ACCESS
25 #define CPPCODEC_DETAIL_DATA_ACCESS
26
27 #include <stdint.h> // for size_t
28 #include <string> // for static_assert() checking that string will be optimized
29 #include <type_traits> // for std::enable_if and such
30 #include <vector> // for static_assert() checking that vector will be optimized
31
32 #include "../detail/config.hpp" // for CPPCODEC_ALWAYS_INLINE
33
34 namespace cppcodec {
35 namespace data {
36
37 // This file contains a number of templated data accessors that can be
38 // implemented in the cppcodec::data namespace for types that don't fulfill
39 // the default type requirements:
40 // For result types: init(Result&, ResultState&, size_t capacity),
41 //     put(Result&, ResultState&, char), finish(Result&, State&)
42 // For const (read-only) types: char_data(const T&)
43 // For both const and result types: size(const T&)
44
45 template <typename T>
46 CPPCODEC_ALWAYS_INLINE size_t size(const T& t) { return t.size(); }
47
48 template <typename T, size_t N>
49 CPPCODEC_ALWAYS_INLINE constexpr size_t size(const T (&t)[N]) noexcept {
50     return N * sizeof(t[0]);
51 }
52
53 class general_t {};
54 class specific_t : public general_t {};
55
56 class empty_result_state {
57     template <typename Result>
58     CPPCODEC_ALWAYS_INLINE void size(const Result& result) { return size(result); }
59 };
60
61 // SFINAE: Generic fallback in case no specific state function applies.
62 template <typename Result>
63 CPPCODEC_ALWAYS_INLINE empty_result_state create_state(Result&, general_t)
64 {
65     return empty_result_state();
66 }
67
68 //
69 // Generic templates for containers: Use these init()/put()/finish()
70 // implementations if no specialization was found.
71 //
72
73 template <typename Result>
74 CPPCODEC_ALWAYS_INLINE void init(Result& result, empty_result_state&, size_t capacity)
75 {
76     result.resize(0);
77     result.reserve(capacity);
78 }
79
80 template <typename Result>
81 CPPCODEC_ALWAYS_INLINE void finish(Result&, empty_result_state&)
82 {
83     // Default is to push_back(), which already increases the size.
84 }
85
86 // For the put() default implementation, we try calling push_back() with either uint8_t or char,
87 // whichever compiles. Scary-fancy template magic from http://stackoverflow.com/a/1386390.
88 namespace fallback {
89     struct flag { char c[2]; }; // sizeof > 1
90     flag put_uint8(...);
91
92     int operator,(flag, flag);
93     template <typename T> void operator,(flag, T&); // map everything else to void
94     char operator,(int, flag); // sizeof 1
95 }
96
97 template <typename Result> inline void put_uint8(Result& result, uint8_t c) { result.push_back(c); }
98
99 template <bool> struct put_impl;
100 template <> struct put_impl<true> { // put_uint8() available
101     template<typename Result>
102     static CPPCODEC_ALWAYS_INLINE void put(Result& result, uint8_t c)
103     {
104         put_uint8(result, c);
105     }
106 };
107 template <> struct put_impl<false> { // put_uint8() not available
108     template<typename Result>
109     static CPPCODEC_ALWAYS_INLINE void put(Result& result, uint8_t c)
110     {
111         result.push_back(static_cast<char>(c));
112     }
113 };
114
115 template <typename Result>
116 CPPCODEC_ALWAYS_INLINE void put(Result& result, empty_result_state&, uint8_t c)
117 {
118     using namespace fallback;
119     put_impl<sizeof(fallback::flag(), put_uint8(result, c), fallback::flag()) != 1>::put(result, c);
120 }
121
122 //
123 // Specialization for container types with direct mutable data access,
124 // e.g. std::vector<uint8_t>.
125 //
126 // The expected way to specialize is to draft a new xyz_result_state type and
127 // return an instance of it from a create_state() template specialization.
128 // You can then create overloads for init(), put() and finish()
129 // for the new result state type.
130 //
131 // If desired, a non-templated overload for both specific types
132 // (result & state) can be added to tailor it to that particular result type.
133 //
134
135 template <typename T>
136 constexpr auto data_is_mutable(T* t) -> decltype(t->data()[size_t(0)] = 'x', bool())
137 {
138     return true;
139 }
140 constexpr bool data_is_mutable(...) { return false; }
141
142 template <typename Result>
143 class direct_data_access_result_state
144 {
145 public:
146     CPPCODEC_ALWAYS_INLINE void init(Result& result, size_t capacity)
147     {
148         // reserve() may not actually allocate the storage right away,
149         // and it isn't guaranteed that it will be untouched upon the
150         //.next resize(). In that light, resize from the start and
151         // slightly reduce the size at the end if necessary.
152         result.resize(capacity);
153     }
154     CPPCODEC_ALWAYS_INLINE void put(Result& result, char c)
155     {
156         result.data()[m_offset++] = c;
157     }
158     CPPCODEC_ALWAYS_INLINE void finish(Result& result)
159     {
160         result.resize(m_offset);
161     }
162     CPPCODEC_ALWAYS_INLINE size_t size(const Result&)
163     {
164         return m_offset;
165     }
166 private:
167     size_t m_offset = 0;
168 };
169
170 // SFINAE: Select a specific state based on the result type and possible result state type.
171 // Implement this if direct data access (`result.data()[0] = 'x') isn't already possible
172 // and you want to specialize it for your own result type.
173 // Note: The enable_if should ideally be part of the class declaration,
174 //       but Visual Studio C++ will not compile it that way.
175 //       Have it here in the factory function instead.
176 template <typename Result,
177           typename = typename std::enable_if<
178                   data_is_mutable((Result*)nullptr)>::type>
179 CPPCODEC_ALWAYS_INLINE direct_data_access_result_state<Result> create_state(Result&, specific_t)
180 {
181     return direct_data_access_result_state<Result>();
182 }
183
184 static_assert(std::is_same<
185         decltype(create_state(*(std::vector<uint8_t>*)nullptr, specific_t())),
186         direct_data_access_result_state<std::vector<uint8_t>>>::value,
187         "std::vector<uint8_t> must be handled by direct_data_access_result_state");
188
189 // Specialized init(), put() and finish() functions for direct_data_access_result_state.
190 template <typename Result>
191 CPPCODEC_ALWAYS_INLINE void init(Result& result, direct_data_access_result_state<Result>& state, size_t capacity)
192 {
193     state.init(result, capacity);
194 }
195
196 template <typename Result>
197 CPPCODEC_ALWAYS_INLINE void put(Result& result, direct_data_access_result_state<Result>& state, char c)
198 {
199     state.put(result, c);
200 }
201
202 template <typename Result>
203 CPPCODEC_ALWAYS_INLINE void finish(Result& result, direct_data_access_result_state<Result>& state)
204 {
205     state.finish(result);
206 }
207
208 //
209 // Specialization for container types with direct mutable array access,
210 // e.g. std::string. This is generally faster because bound checks are
211 // minimal and operator[] is more likely noexcept. In addition,
212 // std::string::push_back() needs to write a null character on every
213 // expansion, which should be more efficient when done in bulk by resize().
214 //
215 // Compared to the above, tracking an extra offset variable is cheap.
216 //
217
218 template <typename T>
219 constexpr auto array_access_is_mutable(T* t) -> decltype((*t)[size_t(0)] = 'x', bool())
220 {
221     return true;
222 }
223 constexpr bool array_access_is_mutable(...) { return false; }
224
225 template <typename Result>
226 class array_access_result_state
227 {
228 public:
229     CPPCODEC_ALWAYS_INLINE void init(Result& result, size_t capacity)
230     {
231         // reserve() may not actually allocate the storage right away,
232         // and it isn't guaranteed that it will be untouched upon the
233         //.next resize(). In that light, resize from the start and
234         // slightly reduce the size at the end if necessary.
235         result.resize(capacity);
236     }
237     CPPCODEC_ALWAYS_INLINE void put(Result& result, char c)
238     {
239         result[m_offset++] = c;
240     }
241     CPPCODEC_ALWAYS_INLINE void finish(Result& result)
242     {
243         result.resize(m_offset);
244     }
245     CPPCODEC_ALWAYS_INLINE size_t size(const Result&)
246     {
247         return m_offset;
248     }
249 private:
250     size_t m_offset = 0;
251 };
252
253 // SFINAE: Select a specific state based on the result type and possible result state type.
254 // Note: The enable_if should ideally be part of the class declaration,
255 //       but Visual Studio C++ will not compile it that way.
256 //       Have it here in the factory function instead.
257 template <typename Result,
258           typename = typename std::enable_if<
259                   !data_is_mutable((Result*)nullptr) // no more than one template option
260                   && array_access_is_mutable((Result*)nullptr)>::type>
261 CPPCODEC_ALWAYS_INLINE array_access_result_state<Result> create_state(Result&, specific_t)
262 {
263     return array_access_result_state<Result>();
264 }
265
266 static_assert(std::is_same<
267         decltype(create_state(*(std::string*)nullptr, specific_t())),
268         array_access_result_state<std::string>>::value,
269         "std::string must be handled by array_access_result_state");
270
271 // Specialized init(), put() and finish() functions for array_access_result_state.
272 template <typename Result>
273 CPPCODEC_ALWAYS_INLINE void init(Result& result, array_access_result_state<Result>& state, size_t capacity)
274 {
275     state.init(result, capacity);
276 }
277
278 template <typename Result>
279 CPPCODEC_ALWAYS_INLINE void put(Result& result, array_access_result_state<Result>& state, char c)
280 {
281     state.put(result, c);
282 }
283
284 template <typename Result>
285 CPPCODEC_ALWAYS_INLINE void finish(Result& result, array_access_result_state<Result>& state)
286 {
287     state.finish(result);
288 }
289
290 // char_data() is only used to read, not for result buffers.
291 template <typename T> inline const char* char_data(const T& t)
292 {
293     return reinterpret_cast<const char*>(t.data());
294 }
295 template <typename T, size_t N> inline const char* char_data(const T (&t)[N]) noexcept
296 {
297     return reinterpret_cast<const char*>(&(t[0]));
298 }
299
300 template <typename T> inline const uint8_t* uchar_data(const T& t)
301 {
302     return reinterpret_cast<const uint8_t*>(char_data(t));
303 }
304
305 } // namespace data
306 } // namespace cppcodec
307
308 #endif