access.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. #ifndef CPPCODEC_DETAIL_DATA_ACCESS
  25. #define CPPCODEC_DETAIL_DATA_ACCESS
  26. #include <stdint.h> // for size_t
  27. #include <string> // for static_assert() checking that string will be optimized
  28. #include <type_traits> // for std::enable_if, std::remove_reference, and such
  29. #include <utility> // for std::declval
  30. #include <vector> // for static_assert() checking that vector will be optimized
  31. #include "../detail/config.hpp" // for CPPCODEC_ALWAYS_INLINE
  32. namespace cppcodec {
  33. namespace data {
  34. // This file contains a number of templated data accessors that can be
  35. // implemented in the cppcodec::data namespace for types that don't fulfill
  36. // the default type requirements:
  37. // For result types: init(Result&, ResultState&, size_t capacity),
  38. // put(Result&, ResultState&, char), finish(Result&, State&)
  39. // For const (read-only) types: char_data(const T&)
  40. // For both const and result types: size(const T&)
  41. template <typename T>
  42. CPPCODEC_ALWAYS_INLINE size_t size(const T& t) { return t.size(); }
  43. template <typename T, size_t N>
  44. CPPCODEC_ALWAYS_INLINE constexpr size_t size(const T (&t)[N]) noexcept {
  45. return (void)t, N * sizeof(t[0]);
  46. }
  47. class general_t {};
  48. class specific_t : public general_t {};
  49. class empty_result_state {
  50. template <typename Result>
  51. CPPCODEC_ALWAYS_INLINE void size(const Result& result) { return size(result); }
  52. };
  53. // SFINAE: Generic fallback in case no specific state function applies.
  54. template <typename Result>
  55. CPPCODEC_ALWAYS_INLINE empty_result_state create_state(Result&, general_t)
  56. {
  57. return empty_result_state();
  58. }
  59. //
  60. // Generic templates for containers: Use these init()/put()/finish()
  61. // implementations if no specialization was found.
  62. //
  63. template <typename Result>
  64. CPPCODEC_ALWAYS_INLINE void init(Result& result, empty_result_state&, size_t capacity)
  65. {
  66. result.resize(0);
  67. result.reserve(capacity);
  68. }
  69. template <typename Result>
  70. CPPCODEC_ALWAYS_INLINE void finish(Result&, empty_result_state&)
  71. {
  72. // Default is to push_back(), which already increases the size.
  73. }
  74. // For the put() default implementation, we try calling push_back() with either uint8_t or char,
  75. // whichever compiles. Scary-fancy template magic from http://stackoverflow.com/a/1386390.
  76. namespace fallback {
  77. struct flag { char c[2]; }; // sizeof > 1
  78. flag put_uint8(...);
  79. int operator,(flag, flag);
  80. template <typename T> void operator,(flag, T&); // map everything else to void
  81. char operator,(int, flag); // sizeof 1
  82. }
  83. template <typename Result> inline void put_uint8(Result& result, uint8_t c) { result.push_back(c); }
  84. template <bool> struct put_impl;
  85. template <> struct put_impl<true> { // put_uint8() available
  86. template<typename Result>
  87. static CPPCODEC_ALWAYS_INLINE void put(Result& result, uint8_t c)
  88. {
  89. put_uint8(result, c);
  90. }
  91. };
  92. template <> struct put_impl<false> { // put_uint8() not available
  93. template<typename Result>
  94. static CPPCODEC_ALWAYS_INLINE void put(Result& result, uint8_t c)
  95. {
  96. result.push_back(static_cast<char>(c));
  97. }
  98. };
  99. template <typename Result>
  100. CPPCODEC_ALWAYS_INLINE void put(Result& result, empty_result_state&, uint8_t c)
  101. {
  102. using namespace fallback;
  103. put_impl<sizeof(fallback::flag(), put_uint8(result, c), fallback::flag()) != 1>::put(result, c);
  104. }
  105. //
  106. // Specialization for container types with direct mutable data access,
  107. // e.g. std::vector<uint8_t>.
  108. //
  109. // The expected way to specialize is to draft a new xyz_result_state type and
  110. // return an instance of it from a create_state() template specialization.
  111. // You can then create overloads for init(), put() and finish()
  112. // for the new result state type.
  113. //
  114. // If desired, a non-templated overload for both specific types
  115. // (result & state) can be added to tailor it to that particular result type.
  116. //
  117. template <typename T>
  118. constexpr auto data_is_mutable(T* t) -> decltype(t->data()[size_t(0)] = 'x', bool())
  119. {
  120. return (void)t, true;
  121. }
  122. constexpr bool data_is_mutable(...) { return false; }
  123. template <typename Result>
  124. class direct_data_access_result_state
  125. {
  126. public:
  127. CPPCODEC_ALWAYS_INLINE void init(Result& result, size_t capacity)
  128. {
  129. // reserve() may not actually allocate the storage right away,
  130. // and it isn't guaranteed that it will be untouched upon the
  131. //.next resize(). In that light, resize from the start and
  132. // slightly reduce the size at the end if necessary.
  133. result.resize(capacity);
  134. // result.data() may perform a calculation to retrieve the address.
  135. // E.g. std::string (since C++11) will use small string optimization,
  136. // so it needs to check if it's using allocated data or (ab)using
  137. // its own member variables interpreted as char array.
  138. // (This result_state is used for std::string starting with C++17.)
  139. // Conditional code paths are slow so we only do it once, at the start.
  140. m_buffer = result.data();
  141. }
  142. CPPCODEC_ALWAYS_INLINE void put(Result&, char c)
  143. {
  144. m_buffer[m_offset++] = c;
  145. }
  146. CPPCODEC_ALWAYS_INLINE void finish(Result& result)
  147. {
  148. result.resize(m_offset);
  149. }
  150. CPPCODEC_ALWAYS_INLINE size_t size(const Result&)
  151. {
  152. return m_offset;
  153. }
  154. private:
  155. // Make sure to get the mutable buffer decltype by using assignment.
  156. typename std::remove_reference<
  157. decltype(std::declval<Result>().data()[size_t(0)] = 'x')>::type* m_buffer;
  158. size_t m_offset = 0;
  159. };
  160. // SFINAE: Select a specific state based on the result type and possible result state type.
  161. // Implement this if direct data access (`result.data()[0] = 'x') isn't already possible
  162. // and you want to specialize it for your own result type.
  163. // Note: The enable_if should ideally be part of the class declaration,
  164. // but Visual Studio C++ will not compile it that way.
  165. // Have it here in the factory function instead.
  166. template <typename Result,
  167. typename = typename std::enable_if<
  168. data_is_mutable(static_cast<Result*>(nullptr))>::type>
  169. CPPCODEC_ALWAYS_INLINE direct_data_access_result_state<Result> create_state(Result&, specific_t)
  170. {
  171. return direct_data_access_result_state<Result>();
  172. }
  173. static_assert(std::is_same<
  174. decltype(create_state(*static_cast<std::vector<uint8_t>*>(nullptr), specific_t())),
  175. direct_data_access_result_state<std::vector<uint8_t>>>::value,
  176. "std::vector<uint8_t> must be handled by direct_data_access_result_state");
  177. // Specialized init(), put() and finish() functions for direct_data_access_result_state.
  178. template <typename Result>
  179. CPPCODEC_ALWAYS_INLINE void init(Result& result, direct_data_access_result_state<Result>& state, size_t capacity)
  180. {
  181. state.init(result, capacity);
  182. }
  183. template <typename Result>
  184. CPPCODEC_ALWAYS_INLINE void put(Result& result, direct_data_access_result_state<Result>& state, char c)
  185. {
  186. state.put(result, c);
  187. }
  188. template <typename Result>
  189. CPPCODEC_ALWAYS_INLINE void finish(Result& result, direct_data_access_result_state<Result>& state)
  190. {
  191. state.finish(result);
  192. }
  193. //
  194. // Specialization for container types with direct mutable array access,
  195. // e.g. std::string. This is generally faster because bound checks are
  196. // minimal and operator[] is more likely noexcept. In addition,
  197. // std::string::push_back() needs to write a null character on every
  198. // expansion, which should be more efficient when done in bulk by resize().
  199. //
  200. // Compared to the above, tracking an extra offset variable is cheap.
  201. //
  202. template <typename T>
  203. constexpr auto array_access_is_mutable(T* t) -> decltype((*t)[size_t(0)] = 'x', bool())
  204. {
  205. return (void)t, true;
  206. }
  207. constexpr bool array_access_is_mutable(...) { return false; }
  208. template <typename Result>
  209. class array_access_result_state
  210. {
  211. public:
  212. CPPCODEC_ALWAYS_INLINE void init(Result& result, size_t capacity)
  213. {
  214. // reserve() may not actually allocate the storage right away,
  215. // and it isn't guaranteed that it will be untouched upon the
  216. //.next resize(). In that light, resize from the start and
  217. // slightly reduce the size at the end if necessary.
  218. result.resize(capacity);
  219. }
  220. CPPCODEC_ALWAYS_INLINE void put(Result& result, char c)
  221. {
  222. result[m_offset++] = c;
  223. }
  224. CPPCODEC_ALWAYS_INLINE void finish(Result& result)
  225. {
  226. result.resize(m_offset);
  227. }
  228. CPPCODEC_ALWAYS_INLINE size_t size(const Result&)
  229. {
  230. return m_offset;
  231. }
  232. private:
  233. size_t m_offset = 0;
  234. };
  235. // SFINAE: Select a specific state based on the result type and possible result state type.
  236. // Note: The enable_if should ideally be part of the class declaration,
  237. // but Visual Studio C++ will not compile it that way.
  238. // Have it here in the factory function instead.
  239. template <typename Result,
  240. typename = typename std::enable_if<
  241. !data_is_mutable(static_cast<Result*>(nullptr)) // no more than one template option
  242. && array_access_is_mutable(static_cast<Result*>(nullptr))>::type>
  243. CPPCODEC_ALWAYS_INLINE array_access_result_state<Result> create_state(Result&, specific_t)
  244. {
  245. return array_access_result_state<Result>();
  246. }
  247. #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L)
  248. static_assert(std::is_same<
  249. decltype(create_state(*static_cast<std::string*>(nullptr), specific_t())),
  250. direct_data_access_result_state<std::string>>::value,
  251. "std::string (C++17 and later) must be handled by direct_data_access_result_state");
  252. #elif __cplusplus < 201703 && !defined(_MSVC_LANG) // we can't trust MSVC to set this right
  253. static_assert(std::is_same<
  254. decltype(create_state(*static_cast<std::string*>(nullptr), specific_t())),
  255. array_access_result_state<std::string>>::value,
  256. "std::string (pre-C++17) must be handled by array_access_result_state");
  257. #endif
  258. // Specialized init(), put() and finish() functions for array_access_result_state.
  259. template <typename Result>
  260. CPPCODEC_ALWAYS_INLINE void init(Result& result, array_access_result_state<Result>& state, size_t capacity)
  261. {
  262. state.init(result, capacity);
  263. }
  264. template <typename Result>
  265. CPPCODEC_ALWAYS_INLINE void put(Result& result, array_access_result_state<Result>& state, char c)
  266. {
  267. state.put(result, c);
  268. }
  269. template <typename Result>
  270. CPPCODEC_ALWAYS_INLINE void finish(Result& result, array_access_result_state<Result>& state)
  271. {
  272. state.finish(result);
  273. }
  274. // char_data() is only used to read, not for result buffers.
  275. template <typename T> inline const char* char_data(const T& t)
  276. {
  277. return reinterpret_cast<const char*>(t.data());
  278. }
  279. template <typename T, size_t N> inline const char* char_data(const T (&t)[N]) noexcept
  280. {
  281. return reinterpret_cast<const char*>(&(t[0]));
  282. }
  283. template <typename T> inline const uint8_t* uchar_data(const T& t)
  284. {
  285. return reinterpret_cast<const uint8_t*>(char_data(t));
  286. }
  287. } // namespace data
  288. } // namespace cppcodec
  289. #endif