open62541pp 0.22.0
C++ wrapper of open62541
Loading...
Searching...
No Matches
types_handling.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // transform
4#include <cassert>
5#include <cstring> // memcpy
6#include <iterator>
7#include <memory>
8#include <new> // bad_alloc
9#include <type_traits>
10#include <utility> // forward
11
13#include "open62541pp/detail/traits.hpp" // IterValueT
15
16namespace opcua::detail {
17
18/* ------------------------------------ Generic type handling ----------------------------------- */
19
20template <typename T>
21using IsPointerFree = std::disjunction<
22 std::is_integral<T>,
23 std::is_floating_point<T>,
24 std::is_enum<T>,
25 std::is_same<T, UA_Guid>>;
26
27template <typename T>
28constexpr bool isValidTypeCombination(const UA_DataType& type) {
29 if constexpr (std::is_void_v<T>) {
30 return true; // allow type-erasure
31 } else {
32 return sizeof(T) == type.memSize;
33 }
34}
35
36template <typename T>
37constexpr void clear(T& native, const UA_DataType& type) noexcept {
38 assert(isValidTypeCombination<T>(type));
39 if constexpr (IsPointerFree<T>::value) {
40 native = {};
41 } else {
42 UA_clear(&native, &type);
43 }
44}
45
46template <typename T>
47void deallocate(T* native) noexcept {
48 UA_free(native); // NOLINT
49}
50
51template <typename T>
52void deallocate(T* native, const UA_DataType& type) noexcept {
53 assert(isValidTypeCombination<T>(type));
54 if (native != nullptr) {
55 clear(*native, type);
56 deallocate(native);
57 }
58}
59
60template <typename T>
61[[nodiscard]] T* allocate() {
62 auto* ptr = static_cast<T*>(UA_calloc(1, sizeof(T))); // NOLINT
63 if (ptr == nullptr) {
64 throw std::bad_alloc{};
65 }
66 return ptr;
67}
68
69template <
70 typename T,
71 typename Deleter,
72 typename = std::enable_if_t<std::is_invocable_v<Deleter, T*>>>
73[[nodiscard]] auto makeUnique(Deleter&& deleter) {
74 return std::unique_ptr<T, std::decay_t<Deleter>>{allocate<T>(), std::forward<Deleter>(deleter)};
75}
76
77template <typename T>
78[[nodiscard]] auto makeUnique(const UA_DataType& type) {
79 return makeUnique<T>([&type](T* native) { deallocate(native, type); });
80}
81
82template <typename T>
83[[nodiscard]] constexpr T copy(const T& src, const UA_DataType& type) {
84 assert(isValidTypeCombination<T>(type));
85 if constexpr (IsPointerFree<T>::value) {
86 return src;
87 } else {
88 T dst; // NOLINT, initialized in UA_copy function
89 throwIfBad(UA_copy(&src, &dst, &type));
90 return dst;
91 }
92}
93
94template <typename T>
95[[nodiscard]] constexpr auto copy(T&& src, const UA_DataType& type) {
96 if constexpr (std::is_rvalue_reference_v<decltype(src)>) {
97 return std::forward<T>(src);
98 } else {
99 return copy(std::as_const(src), type);
100 }
101}
102
103/* ----------------------------------- Generic array handling ----------------------------------- */
104
105/**
106 * Sentinel for empty but defined arrays.
107 *
108 * In OPC UA, arrays can have a length of zero or more with the usual meaning.
109 * In addition, arrays can be undefined. Then, they don't even have a length.
110 * In the binary encoding, this is indicated by an array of length -1.
111 *
112 * In open62541, size_t is used for array lengths.
113 * An undefined array has length 0 and the data pointer is NULL.
114 * An array of length 0 also has length 0 but a data pointer UA_EMPTY_ARRAY_SENTINEL (0x01).
115 */
116inline constexpr uintptr_t emptyArraySentinel = 0x01;
117
118template <typename T>
119[[nodiscard]] T* makeEmptyArraySentinel() noexcept {
120 return reinterpret_cast<T*>(emptyArraySentinel); // NOLINT
121}
122
123template <typename T>
124[[nodiscard]] T* stripEmptyArraySentinel(T* array) noexcept {
125 // NOLINTNEXTLINE
126 return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(array) & ~emptyArraySentinel);
127}
128
129template <typename T>
130bool isEmptyArray(T* array, size_t size) noexcept {
131 return stripEmptyArraySentinel(array) == nullptr || size == 0;
132}
133
134template <typename T>
135void clearArray(T* array, size_t size, const UA_DataType& type) noexcept {
136 if (isEmptyArray(array, size)) {
137 return;
138 }
139 if constexpr (IsPointerFree<T>::value) {
140 std::memset(array, 0, size * sizeof(T));
141 } else {
142 for (size_t i = 0; i < size; ++i) {
143 clear(array[i], type); // NOLINT(*pointer-arithmetic)
144 }
145 }
146}
147
148template <typename T>
149void deallocateArray(T* array) noexcept {
150 UA_free(stripEmptyArraySentinel(array)); // NOLINT
151}
152
153template <typename T>
154void deallocateArray(T* array, size_t size, const UA_DataType& type) noexcept {
155 assert(isValidTypeCombination<T>(type));
156 clearArray(array, size, type);
157 deallocateArray(array);
158}
159
160template <typename T>
161[[nodiscard]] T* allocateArray(size_t size) {
162 if (size > UA_INT32_MAX) {
163 throw std::bad_alloc{};
164 }
165 if (size == 0) {
166 return makeEmptyArraySentinel<T>();
167 }
168 auto* ptr = static_cast<T*>(UA_calloc(size, sizeof(T))); // NOLINT
169 if (ptr == nullptr) {
170 throw std::bad_alloc{};
171 }
172 return ptr;
173}
174
175template <
176 typename T,
177 typename Deleter,
178 typename = std::enable_if_t<std::is_invocable_v<Deleter, T*>>>
179[[nodiscard]] auto makeUniqueArray(size_t size, Deleter&& deleter) {
180 // NOLINTNEXTLINE(*c-arrays)
181 return std::unique_ptr<T[], std::decay_t<Deleter>>{
182 allocateArray<T>(size), std::forward<Deleter>(deleter)
183 };
184}
185
186template <typename T>
187[[nodiscard]] auto makeUniqueArray(size_t size, const UA_DataType& type) {
188 return makeUniqueArray<T>(size, [&type, size](T* native) {
189 deallocateArray(native, size, type);
190 });
191}
192
193template <typename InputIt>
194[[nodiscard]] std::pair<IterValueT<InputIt>*, size_t> copyArray(
195 InputIt first, InputIt last, const UA_DataType& type, std::forward_iterator_tag /* unused */
196) {
197 using ValueType = IterValueT<InputIt>;
198 const size_t size = std::distance(first, last);
199 auto dst = makeUniqueArray<ValueType>(size, type);
200 std::transform(first, last, dst.get(), [&](auto&& item) {
201 return copy<ValueType>(std::forward<decltype(item)>(item), type);
202 });
203 return {dst.release(), size};
204}
205
206template <typename InputIt>
207[[nodiscard]] std::pair<IterValueT<InputIt>*, size_t> copyArray(
208 InputIt first, InputIt last, const UA_DataType& type, std::input_iterator_tag /* unused */
209) {
210 using ValueType = IterValueT<InputIt>;
211 if (first == last) {
212 return {makeEmptyArraySentinel<ValueType>(), 0};
213 }
214
215 size_t index = 0;
216 size_t capacity = 16; // initial capacity to avoid frequency reallocations
217 auto dst = makeUniqueArray<ValueType>(capacity, [&](ValueType* ptr) {
218 deallocateArray(ptr, index, type);
219 });
220
221 const auto reallocate = [&](size_t newSize) {
222 // resize without clearing members; newSize must be greater than number of members
223 auto* ptr = static_cast<ValueType*>(UA_realloc(dst.get(), newSize * sizeof(ValueType)));
224 if (ptr == nullptr) {
225 throw std::bad_alloc{};
226 }
227 dst.release(); // realloc frees old memory on success; safe to release before reset
228 dst.reset(ptr);
229 };
230
231 for (auto it = first; it != last; ++it, ++index) {
232 if (index >= capacity) {
233 capacity *= 2;
234 reallocate(capacity);
235 }
236 dst[index] = copy<ValueType>(*it, type);
237 }
238 reallocate(index);
239 return {dst.release(), index};
240}
241
242template <typename InputIt>
243[[nodiscard]] std::pair<IterValueT<InputIt>*, size_t> copyArray(
244 InputIt first, InputIt last, const UA_DataType& type
245) {
246 return copyArray(first, last, type, IterCategoryT<InputIt>{});
247}
248
249template <typename T>
250[[nodiscard]] T* copyArray(const T* src, size_t size) {
251 if (isEmptyArray(src, size)) {
252 return makeEmptyArraySentinel<T>();
253 }
254 T* dst = allocateArray<T>(size);
255 std::memcpy(dst, src, size * sizeof(T));
256 return dst;
257}
258
259template <typename T>
260[[nodiscard]] T* copyArray(const T* src, size_t size, const UA_DataType& type) {
261 return IsPointerFree<T>::value
262 ? copyArray(src, size)
263 : copyArray(src, src + size, type).first; // NOLINT
264}
265
266} // namespace opcua::detail
#define UA_free
#define UA_realloc
#define UA_calloc
constexpr void throwIfBad(UA_StatusCode code)
Check the status code and throw a BadStatus exception if the status code is bad.
Definition exception.hpp:85
UA_UInt32 memSize
void * dst
void UA_clear(void *p, const UA_DataType *type)
const UA_DataType * type