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