MDBX Containers
Loading...
Searching...
No Matches
utils.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef _MDBX_CONTAINERS_UTILS_HPP_INCLUDED
3#define _MDBX_CONTAINERS_UTILS_HPP_INCLUDED
4
8
11
12#if __cplusplus >= 201703L
13# define MDBXC_NODISCARD [[nodiscard]]
14#else
15# define MDBXC_NODISCARD
16#endif
17
18namespace mdbxc {
19
23 void check_mdbx(int rc, const std::string& context) {
24 if (rc != MDBX_SUCCESS) {
25 throw MdbxException(context + ": (" + std::to_string(rc) + ") " + std::string(mdbx_strerror(rc)), rc);
26 }
27 }
28
32 inline uint32_t sortable_key_from_float(float f) {
33 uint32_t u;
34 std::memcpy(&u, &f, sizeof(uint32_t));
35 return (u & 0x80000000u) ? ~u : (u ^ 0x80000000u);
36 }
37
41 inline uint64_t sortable_key_from_double(double d) {
42 uint64_t u;
43 std::memcpy(&u, &d, sizeof(uint64_t));
44 return (u & 0x8000000000000000ull) ? ~u : (u ^ 0x8000000000000000ull);
45 }
46
47 // --- Traits ---
48
51 template <typename T>
52 struct has_to_bytes {
53 private:
54 template <typename U>
55 static auto check(U*) -> decltype(std::declval<const U>().to_bytes(), std::true_type());
56 template <typename>
57 static std::false_type check(...);
58 public:
59 static const bool value = decltype(check<T>(0))::value;
60 };
61
64 template <typename T>
66 private:
67 template <typename U>
68 static auto check(U*) -> decltype(U::from_bytes((const void*)0, size_t(0)), std::true_type());
69 template <typename>
70 static std::false_type check(...);
71 public:
72 static const bool value = decltype(check<T>(0))::value;
73 };
74
77 template <typename T>
79 private:
80 template <typename U>
81 static auto check(U*) -> decltype(typename U::value_type(), std::true_type());
82 template <typename>
83 static std::false_type check(...);
84 public:
85 static const bool value = decltype(check<T>(0))::value;
86 };
87
88//-----------------------------------------------------------------------------
89
93 template<typename T>
94 inline MDBX_db_flags_t get_mdbx_flags() {
95 return
96 std::is_same<T, int>::value || std::is_same<T, int32_t>::value ||
97 std::is_same<T, uint32_t>::value || std::is_same<T, int64_t>::value ||
98 std::is_same<T, uint64_t>::value || std::is_same<T, float>::value ||
99 std::is_same<T, double>::value || std::is_same<T, char>::value ||
100 std::is_same<T, unsigned char>::value
101 ? MDBX_INTEGERKEY : static_cast<MDBX_db_flags_t>(0);
102 }
103
104//-----------------------------------------------------------------------------
105
110 template<typename T>
111 size_t get_key_size(const T& key) {
112 // std::string
113 if (std::is_same<T, std::string>::value) {
114 return key.size();
115 }
116
117 // 32-bit types
118 if (std::is_same<T, int>::value ||
119 std::is_same<T, int32_t>::value ||
120 std::is_same<T, uint32_t>::value ||
121 std::is_same<T, float>::value) {
122 return sizeof(uint32_t);
123 }
124
125 // 64-bit types
126 if (std::is_same<T, int64_t>::value ||
127 std::is_same<T, uint64_t>::value ||
128 std::is_same<T, double>::value) {
129 return sizeof(uint64_t);
130 }
131
132 // byte vectors
133 if (
134# if __cplusplus >= 201703L
135 std::is_same<T, std::vector<std::byte> >::value ||
136# endif
137 std::is_same<T, std::vector<uint8_t> >::value ||
138 std::is_same<T, std::vector<char> >::value ||
139 std::is_same<T, std::vector<unsigned char> >::value) {
140 return key.size();
141 }
142
143 // fallback
144 return sizeof(T);
145 }
146
169 alignas(8) unsigned char small[16];
170 std::vector<uint8_t> bytes;
171
174 MDBXC_NODISCARD static inline MDBX_val view(const void* p, size_t n) noexcept {
175 MDBX_val v;
176 v.iov_base = (n ? const_cast<void*>(p) : nullptr);
177 v.iov_len = n;
178 return v;
179 }
180
182 MDBXC_NODISCARD inline MDBX_val view_copy(const void* p, size_t n) {
183 bytes.clear();
184 bytes.resize(n);
185 if (n) std::memcpy(bytes.data(), p, n);
186 MDBX_val v;
187 v.iov_base = (bytes.empty() ? nullptr : static_cast<void*>(bytes.data()));
188 v.iov_len = bytes.size();
189 return v;
190 }
191
193 MDBXC_NODISCARD inline MDBX_val view_bytes() const noexcept {
194 MDBX_val v;
195 v.iov_base = (bytes.empty() ? nullptr : const_cast<void*>(static_cast<const void*>(bytes.data())));
196 v.iov_len = bytes.size();
197 return v;
198 }
199
202 MDBXC_NODISCARD inline MDBX_val view_small_copy(const void* p, size_t n) noexcept {
203 std::memcpy(small, p, n);
204 MDBX_val v;
205 v.iov_base = (n ? static_cast<void*>(small) : nullptr);
206 v.iov_len = n;
207 return v;
208 }
209
211 inline void assign_bytes(const void* p, size_t n) {
212 bytes.clear();
213 bytes.resize(n);
214 if (n) std::memcpy(bytes.data(), p, n);
215 }
216
218 inline void clear() noexcept { bytes.clear(); bytes.shrink_to_fit(); }
219 };
220
221 // --- serialize_key overloads ---
222
223
228 template <typename T>
229 typename std::enable_if<!has_to_bytes<T>::value && !std::is_same<T, std::string>::value && !std::is_trivially_copyable<T>::value, MDBX_val>::type
230 serialize_key(const T& key, SerializeScratch& sc) {
231 (void)key;
232 (void)sc;
233 static_assert(sizeof(T) == 0, "Unsupported type for serialize_key");
234 MDBX_val val;
235 val.iov_base = nullptr;
236 val.iov_len = 0;
237 return val;
238 }
239
242 template<typename T>
243 typename std::enable_if<std::is_same<T, std::string>::value, MDBX_val>::type
244 serialize_key(const T& key, SerializeScratch& sc) {
245 (void)sc;
246 return SerializeScratch::view(static_cast<const void*>(key.data()), key.size());
247 }
248
251 template<typename T>
252 typename std::enable_if<
253# if __cplusplus >= 201703L
254 std::is_same<T, std::vector<std::byte>>::value ||
255# endif
256 std::is_same<T, std::vector<uint8_t>>::value ||
257 std::is_same<T, std::vector<char>>::value ||
258 std::is_same<T, std::vector<unsigned char>>::value, MDBX_val>::type
259 serialize_key(const T& key, SerializeScratch& sc) {
260 (void)sc;
261 return SerializeScratch::view(static_cast<const void*>(key.data()), key.size());
262 }
263
266 template<typename T>
267 typename std::enable_if<
268 std::is_integral<T>::value &&
269 (sizeof(T) <= 2), MDBX_val>::type
270 serialize_key(const T& key, SerializeScratch& sc) {
271 static_assert(sizeof(uint32_t) == 4, "Expected 4-byte wrapper");
272 uint32_t temp = static_cast<uint32_t>(key);
273 return sc.view_small_copy(&temp, sizeof(uint32_t));
274 }
275
278 template<typename T>
279 typename std::enable_if<
280 std::is_same<T, int32_t>::value ||
281 std::is_same<T, uint32_t>::value, MDBX_val>::type
282 serialize_key(const T& key, SerializeScratch& sc) {
283 static_assert(sizeof(uint32_t) == 4, "Expected 4-byte integer");
284# if MDBXC_SAFE_INTEGERKEY
285 return sc.view_small_copy(&key, sizeof(uint32_t));
286# else
287 (void)sc;
288 return SerializeScratch::view(static_cast<const void*>(&key), sizeof(uint32_t));
289# endif
290 }
291
294 template<typename T>
295 typename std::enable_if<
296 std::is_same<T, float>::value, MDBX_val>::type
297 serialize_key(const T& key, SerializeScratch& sc) {
298 static_assert(sizeof(uint32_t) == 4, "Expected 4-byte integer");
299 uint32_t temp = sortable_key_from_float(key);
300 return sc.view_small_copy(&temp, sizeof(uint32_t));
301 }
302
305 template<typename T>
306 typename std::enable_if<
307 std::is_same<T, int64_t>::value ||
308 std::is_same<T, uint64_t>::value, MDBX_val>::type
309 serialize_key(const T& key, SerializeScratch& sc) {
310 static_assert(sizeof(uint64_t) == 8, "Expected 8-byte integer");
311# if MDBXC_SAFE_INTEGERKEY
312 return sc.view_small_copy(&key, sizeof(uint64_t));
313# else
314 (void)sc;
315 return SerializeScratch::view(static_cast<const void*>(&key), sizeof(uint64_t));
316# endif
317 }
318
321 template<typename T>
322 typename std::enable_if<
323 std::is_same<T, double>::value, MDBX_val>::type
324 serialize_key(const T& key, SerializeScratch& sc) {
325 static_assert(sizeof(uint64_t) == 8, "Expected 8-byte integer");
326 uint64_t temp = sortable_key_from_double(key);
327 return sc.view_small_copy(&temp, sizeof(uint64_t));
328 }
329
332 template<typename T>
333 typename std::enable_if<
334 std::is_trivially_copyable<T>::value &&
335 !std::is_same<T, std::string>::value &&
336 !(std::is_integral<T>::value && sizeof(T) <= 2) &&
337 !std::is_same<T, int32_t>::value &&
338 !std::is_same<T, uint32_t>::value &&
339 !std::is_same<T, float>::value &&
340 !std::is_same<T, int64_t>::value &&
341 !std::is_same<T, uint64_t>::value &&
342 !std::is_same<T, double>::value, MDBX_val>::type
343 serialize_key(const T& key, SerializeScratch& sc) {
344 (void)sc;
345 return SerializeScratch::view(static_cast<const void*>(&key), sizeof(T));
346 }
347
351 template <size_t N>
352 inline MDBX_val serialize_key(const std::bitset<N>& data, SerializeScratch& sc) {
353 const size_t num_bytes = (N + 7) / 8;
354 std::array<uint8_t, (N + 7) / 8> buffer;
355 buffer.fill(0);
356 for (size_t i = 0; i < N; ++i) {
357 if (data[i]) buffer[i / 8] |= (1 << (i % 8));
358 }
359 return sc.view_copy(buffer.data(), num_bytes);
360 }
361
362 // --- serialize_value overloads ---
363
368 template <typename T>
369 typename std::enable_if<
371 !std::is_same<T, std::vector<typename T::value_type>>::value &&
372 !std::is_trivially_copyable<typename T::value_type>::value &&
374 !std::is_same<T, std::string>::value &&
375 !std::is_trivially_copyable<T>::value, MDBX_val>::type
376 serialize_value(const T& value, SerializeScratch& sc) {
377 (void)value;
378 (void)sc;
379 static_assert(sizeof(T) == 0, "Unsupported type for serialize_value");
380 MDBX_val val;
381 val.iov_base = nullptr;
382 val.iov_len = 0;
383 return val;
384 }
385
388 template<typename T>
389 typename std::enable_if<std::is_same<T, std::string>::value, MDBX_val>::type
390 serialize_value(const T& value, SerializeScratch& sc) {
391 (void)sc;
392 return SerializeScratch::view(static_cast<const void*>(value.data()), value.size());
393 }
394
398 template <typename T>
399 typename std::enable_if<
401 std::is_trivially_copyable<typename T::value_type>::value &&
402 (
403 std::is_same<T, std::deque<typename T::value_type>>::value ||
404 std::is_same<T, std::list<typename T::value_type>>::value ||
405 std::is_same<T, std::set<typename T::value_type>>::value ||
406 std::is_same<T, std::unordered_set<typename T::value_type>>::value
407 ),
408 MDBX_val>::type
409 serialize_value(const T& container, SerializeScratch& sc) {
410 using Elem = typename T::value_type;
411 sc.bytes.resize(container.size() * sizeof(Elem));
412 auto* out = reinterpret_cast<Elem*>(sc.bytes.data());
413 std::copy(container.begin(), container.end(), out);
414 return sc.view_bytes();
415 }
416
419 template<typename T>
420 typename std::enable_if<
422 std::is_same<T, std::vector<typename T::value_type>>::value &&
423 std::is_trivially_copyable<typename T::value_type>::value,
424 MDBX_val>::type
425 serialize_value(const T& container, SerializeScratch& sc) {
426 using Elem = typename T::value_type;
427 sc.bytes.resize(container.size() * sizeof(Elem));
428 std::memcpy(sc.bytes.data(),
429 container.data(),
430 sc.bytes.size());
431 return sc.view_bytes();
432 }
433
436 template<typename T>
437 typename std::enable_if<has_to_bytes<T>::value, MDBX_val>::type
438 serialize_value(const T& value, SerializeScratch& sc) {
439 sc.bytes = value.to_bytes();
440 return sc.view_bytes();
441 }
442
445 template<typename T>
446 typename std::enable_if<
448 std::is_trivially_copyable<T>::value,
449 MDBX_val>::type
450 serialize_value(const T& value, SerializeScratch& sc) {
451 (void)sc;
452 return SerializeScratch::view(static_cast<const void*>(&value), sizeof(T));
453 }
454
457 template<typename T>
458 typename std::enable_if<
460 std::is_same<typename T::value_type, std::string>::value,
461 MDBX_val>::type
462 serialize_value(const T& container, SerializeScratch& sc) {
463 sc.bytes.clear();
464 for (const auto& str : container) {
465 uint32_t len = static_cast<uint32_t>(str.size());
466 sc.bytes.insert(sc.bytes.end(),
467 reinterpret_cast<const uint8_t*>(&len),
468 reinterpret_cast<const uint8_t*>(&len) + sizeof(uint32_t));
469 sc.bytes.insert(sc.bytes.end(), str.begin(), str.end());
470 }
471 return sc.view_bytes();
472 }
473
474 // --- deserialize_value overloads ---
475
480 template<typename T>
481 typename std::enable_if<
484 !std::is_same<T, std::string>::value &&
485 !std::is_trivially_copyable<T>::value, T>::type
486 deserialize_value(const MDBX_val& val) {
487 (void)val;
488 static_assert(sizeof(T) == 0, "Unsupported type for deserialize_value");
489 T out;
490 return out;
491 }
492
495 template<typename T>
496 typename std::enable_if<std::is_same<T, std::string>::value, T>::type
497 deserialize_value(const MDBX_val& val) {
498 return std::string(static_cast<const char*>(val.iov_base), val.iov_len);
499 }
500
503 template<typename T>
504 typename std::enable_if<
505# if __cplusplus >= 201703L
506 std::is_same<T, std::vector<std::byte>>::value ||
507# endif
508 std::is_same<T, std::vector<uint8_t>>::value ||
509 std::is_same<T, std::vector<char>>::value ||
510 std::is_same<T, std::vector<unsigned char>>::value, T>::type
511 deserialize_value(const MDBX_val& val) {
512 const uint8_t* ptr = static_cast<const uint8_t*>(val.iov_base);
513 return T(ptr, ptr + val.iov_len);
514 }
515
518 template<typename T>
519 typename std::enable_if<
520# if __cplusplus >= 201703L
521 std::is_same<T, std::deque<std::byte>>::value ||
522# endif
523 std::is_same<T, std::deque<uint8_t>>::value ||
524 std::is_same<T, std::deque<char>>::value ||
525 std::is_same<T, std::deque<unsigned char>>::value, T>::type
526 deserialize_value(const MDBX_val& val) {
527 const uint8_t* ptr = static_cast<const uint8_t*>(val.iov_base);
528 return T(ptr, ptr + val.iov_len);
529 }
530
533 template<typename T>
534 typename std::enable_if<
535# if __cplusplus >= 201703L
536 std::is_same<T, std::list<std::byte>>::value ||
537# endif
538 std::is_same<T, std::list<uint8_t>>::value ||
539 std::is_same<T, std::list<char>>::value ||
540 std::is_same<T, std::list<unsigned char>>::value, T>::type
541 deserialize_value(const MDBX_val& val) {
542 const uint8_t* ptr = static_cast<const uint8_t*>(val.iov_base);
543 return T(ptr, ptr + val.iov_len);
544 }
545
548 template<typename T>
549 typename std::enable_if<
551 std::is_same<T, std::vector<typename T::value_type>>::value &&
552 std::is_trivially_copyable<typename T::value_type>::value &&
553# if __cplusplus >= 201703L
554 !std::is_same<T, std::vector<std::byte>>::value &&
555# endif
556 !std::is_same<T, std::vector<uint8_t>>::value &&
557 !std::is_same<T, std::vector<char>>::value &&
558 !std::is_same<T, std::vector<unsigned char>>::value, T>::type
559 deserialize_value(const MDBX_val& val) {
560 typedef typename T::value_type Elem;
561 if (val.iov_len % sizeof(Elem) != 0)
562 throw std::runtime_error("deserialize_value: size not aligned");
563 const size_t count = val.iov_len / sizeof(Elem);
564 const Elem* data = static_cast<const Elem*>(val.iov_base);
565 return T(data, data + count);
566 }
567
570 template<typename T>
571 typename std::enable_if<
572 (std::is_same<T, std::deque<typename T::value_type>>::value ||
573 std::is_same<T, std::list<typename T::value_type>>::value) &&
574 std::is_trivially_copyable<typename T::value_type>::value, T>::type
575 deserialize_value(const MDBX_val& val) {
576 typedef typename T::value_type Elem;
577 if (val.iov_len % sizeof(Elem) != 0) {
578 throw std::runtime_error("deserialize_value: size not aligned");
579 }
580 const size_t count = val.iov_len / sizeof(Elem);
581 const Elem* data = static_cast<const Elem*>(val.iov_base);
582 return T(data, data + count);
583 }
584
587 template<typename T>
588 typename std::enable_if<
589 (
590 std::is_same<T, std::set<typename T::value_type>>::value ||
591 std::is_same<T, std::unordered_set<typename T::value_type>>::value
592 ) &&
593 std::is_trivially_copyable<typename T::value_type>::value,
594 T>::type
595 deserialize_value(const MDBX_val& val) {
596 typedef typename T::value_type Elem;
597 if (val.iov_len % sizeof(Elem) != 0)
598 throw std::runtime_error("deserialize_value: size not aligned");
599 const size_t count = val.iov_len / sizeof(Elem);
600 const Elem* data = static_cast<const Elem*>(val.iov_base);
601 return T(data, data + count);
602 }
603
606 template<typename T>
607 typename std::enable_if<
608 !has_from_bytes<T>::value && std::is_trivially_copyable<T>::value, T>::type
609 deserialize_value(const MDBX_val& val) {
610 if (val.iov_len != sizeof(T)) {
611 throw std::runtime_error("deserialize_value: size mismatch");
612 }
613 T out;
614 std::memcpy(&out, val.iov_base, sizeof(T));
615 return out;
616 }
617
620 template<typename T>
621 typename std::enable_if<has_from_bytes<T>::value, T>::type
622 deserialize_value(const MDBX_val& val) {
623 return T::from_bytes(val.iov_base, val.iov_len);
624 }
625
628 template<typename T>
629 typename std::enable_if<
631 std::is_same<typename T::value_type, std::string>::value,
632 T>::type
633 deserialize_value(const MDBX_val& val) {
634 const uint8_t* ptr = static_cast<const uint8_t*>(val.iov_base);
635 const uint8_t* end = ptr + val.iov_len;
636
637 T result;
638 while (ptr + sizeof(uint32_t) <= end) {
639 uint32_t len;
640 std::memcpy(&len, ptr, sizeof(uint32_t));
641 ptr += sizeof(uint32_t);
642
643 if (ptr + len > end)
644 throw std::runtime_error("deserialize_value: corrupted data (length overflow)");
645
646 result.emplace_back(reinterpret_cast<const char*>(ptr), len);
647 ptr += len;
648 }
649
650 if (ptr != end)
651 throw std::runtime_error("deserialize_value: trailing data after deserialization");
652
653 return result;
654 }
655
658 template<typename T>
659 typename std::enable_if<
660 std::is_same<T, std::set<std::string>>::value ||
661 std::is_same<T, std::unordered_set<std::string>>::value,
662 T>::type
663 deserialize_value(const MDBX_val& val) {
664 const uint8_t* ptr = static_cast<const uint8_t*>(val.iov_base);
665 const uint8_t* end = ptr + val.iov_len;
666 T result;
667
668 while (ptr + sizeof(uint32_t) <= end) {
669 uint32_t len;
670 std::memcpy(&len, ptr, sizeof(uint32_t));
671 ptr += sizeof(uint32_t);
672
673 if (ptr + len > end)
674 throw std::runtime_error("deserialize_value: corrupted data (length overflow)");
675
676 result.insert(std::string(reinterpret_cast<const char*>(ptr), len));
677 ptr += len;
678 }
679
680 if (ptr != end)
681 throw std::runtime_error("deserialize_value: trailing data after deserialization");
682
683 return result;
684 }
685
686}; // namespace mdbxc
687
689
690#undef MDBXC_NODISCARD
691
692#endif // _MDBX_CONTAINERS_UTILS_HPP_INCLUDED
Represents a specific exception for MDBX-related errors.
#define MDBXC_NODISCARD
Definition utils.hpp:15
size_t get_key_size(const T &key)
Returns the size in bytes of a given key type.
Definition utils.hpp:111
std::enable_if<!has_value_type< T >::value &&!std::is_same< T, std::vector< typenameT::value_type > >::value &&!std::is_trivially_copyable< typenameT::value_type >::value &&!has_to_bytes< T >::value &&!std::is_same< T, std::string >::value &&!std::is_trivially_copyable< T >::value, MDBX_val >::type serialize_value(const T &value, SerializeScratch &sc)
Serializes a general value into MDBX_val.
Definition utils.hpp:376
uint32_t sortable_key_from_float(float f)
Convert IEEE754 float to monotonic sortable unsigned int key.
Definition utils.hpp:32
std::enable_if<!has_value_type< T >::value &&!has_from_bytes< T >::value &&!std::is_same< T, std::string >::value &&!std::is_trivially_copyable< T >::value, T >::type deserialize_value(const MDBX_val &val)
Deserializes a value from MDBX_val into type T.
Definition utils.hpp:486
MDBX_db_flags_t get_mdbx_flags()
Returns MDBX flags for a given key type.
Definition utils.hpp:94
std::enable_if<!has_to_bytes< T >::value &&!std::is_same< T, std::string >::value &&!std::is_trivially_copyable< T >::value, MDBX_val >::type serialize_key(const T &key, SerializeScratch &sc)
Serializes a key into MDBX_val for database operations.
Definition utils.hpp:230
uint64_t sortable_key_from_double(double d)
Convert IEEE754 double to monotonic sortable unsigned int key.
Definition utils.hpp:41
void check_mdbx(int rc, const std::string &context)
Throws an MdbxException if MDBX return code indicates an error.
Definition utils.hpp:23
Per-call scratch buffer to produce MDBX_val without using thread_local.
Definition utils.hpp:168
MDBXC_NODISCARD MDBX_val view_small_copy(const void *p, size_t n) noexcept
Copy n bytes into the small inline buffer and return a view.
Definition utils.hpp:202
void clear() noexcept
Optionally clear and release capacity.
Definition utils.hpp:218
MDBXC_NODISCARD MDBX_val view_bytes() const noexcept
Return a view over current bytes (no copy).
Definition utils.hpp:193
unsigned char small[16]
Small inline buffer (16 bytes) aligned to 8 — good for 4/8-byte keys.
Definition utils.hpp:169
std::vector< uint8_t > bytes
Owned dynamic buffer for cases when data must be copied.
Definition utils.hpp:170
static MDBXC_NODISCARD MDBX_val view(const void *p, size_t n) noexcept
Zero-copy view over external memory (no ownership).
Definition utils.hpp:174
void assign_bytes(const void *p, size_t n)
Replace bytes content with a copy of p..p+n .
Definition utils.hpp:211
MDBXC_NODISCARD MDBX_val view_copy(const void *p, size_t n)
Copy n bytes from p into bytes and return a view.
Definition utils.hpp:182
Trait to check if a type provides a static from_bytes() method.
Definition utils.hpp:65
static auto check(U *) -> decltype(U::from_bytes((const void *) 0, size_t(0)), std::true_type())
static std::false_type check(...)
static const bool value
Definition utils.hpp:72
Trait to check if a type provides a to_bytes() member.
Definition utils.hpp:52
static std::false_type check(...)
static auto check(U *) -> decltype(std::declval< const U >().to_bytes(), std::true_type())
static const bool value
Definition utils.hpp:59
Trait indicating that a container defines value_type.
Definition utils.hpp:78
static auto check(U *) -> decltype(typename U::value_type(), std::true_type())
static const bool value
Definition utils.hpp:85
static std::false_type check(...)