open62541pp 0.20.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> // for_each_n, 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 std::for_each_n(array, size, [&](auto& item) { clear(item, type); });
143 }
144}
145
146template <typename T>
147void deallocateArray(T* array) noexcept {
148 UA_free(stripEmptyArraySentinel(array)); // NOLINT
149}
150
151template <typename T>
152void deallocateArray(T* array, size_t size, const UA_DataType& type) noexcept {
153 assert(isValidTypeCombination<T>(type));
154 clearArray(array, size, type);
155 deallocateArray(array);
156}
157
158template <typename T>
159[[nodiscard]] T* allocateArray(size_t size) {
160 if (size > UA_INT32_MAX) {
161 throw std::bad_alloc{};
162 }
163 if (size == 0) {
164 return makeEmptyArraySentinel<T>();
165 }
166 auto* ptr = static_cast<T*>(UA_calloc(size, sizeof(T))); // NOLINT
167 if (ptr == nullptr) {
168 throw std::bad_alloc{};
169 }
170 return ptr;
171}
172
173template <
174 typename T,
175 typename Deleter,
176 typename = std::enable_if_t<std::is_invocable_v<Deleter, T*>>>
177[[nodiscard]] auto makeUniqueArray(size_t size, Deleter&& deleter) {
178 // NOLINTNEXTLINE(*c-arrays)
179 return std::unique_ptr<T[], std::decay_t<Deleter>>{
180 allocateArray<T>(size), std::forward<Deleter>(deleter)
181 };
182}
183
184template <typename T>
185[[nodiscard]] auto makeUniqueArray(size_t size, const UA_DataType& type) {
186 return makeUniqueArray<T>(size, [&type, size](T* native) {
187 deallocateArray(native, size, type);
188 });
189}
190
191template <typename InputIt>
192[[nodiscard]] std::pair<IterValueT<InputIt>*, size_t> copyArray(
193 InputIt first, InputIt last, const UA_DataType& type, std::forward_iterator_tag /* unused */
194) {
195 using ValueType = IterValueT<InputIt>;
196 const size_t size = std::distance(first, last);
197 auto dst = makeUniqueArray<ValueType>(size, type);
198 std::transform(first, last, dst.get(), [&](auto&& item) {
199 return copy<ValueType>(std::forward<decltype(item)>(item), type);
200 });
201 return {dst.release(), size};
202}
203
204template <typename InputIt>
205[[nodiscard]] std::pair<IterValueT<InputIt>*, size_t> copyArray(
206 InputIt first, InputIt last, const UA_DataType& type, std::input_iterator_tag /* unused */
207) {
208 using ValueType = IterValueT<InputIt>;
209 if (first == last) {
210 return {makeEmptyArraySentinel<ValueType>(), 0};
211 }
212
213 size_t index = 0;
214 size_t capacity = 16; // initial capacity to avoid frequency reallocations
215 auto dst = makeUniqueArray<ValueType>(capacity, [&](ValueType* ptr) {
216 deallocateArray(ptr, index, type);
217 });
218
219 const auto reallocate = [&](size_t newSize) {
220 // resize without clearing members; newSize must be greater than number of members
221 auto* ptr = static_cast<ValueType*>(UA_realloc(dst.get(), newSize * sizeof(ValueType)));
222 if (ptr == nullptr) {
223 throw std::bad_alloc{};
224 }
225 dst.release(); // realloc frees old memory on success; safe to release before reset
226 dst.reset(ptr);
227 };
228
229 for (auto it = first; it != last; ++it, ++index) {
230 if (index >= capacity) {
231 capacity *= 2;
232 reallocate(capacity);
233 }
234 dst[index] = copy<ValueType>(*it, type);
235 }
236 reallocate(index);
237 return {dst.release(), index};
238}
239
240template <typename InputIt>
241[[nodiscard]] std::pair<IterValueT<InputIt>*, size_t> copyArray(
242 InputIt first, InputIt last, const UA_DataType& type
243) {
244 return copyArray(first, last, type, IterCategoryT<InputIt>{});
245}
246
247template <typename T>
248[[nodiscard]] T* copyArray(const T* src, size_t size) {
249 if (isEmptyArray(src, size)) {
250 return makeEmptyArraySentinel<T>();
251 }
252 T* dst = allocateArray<T>(size);
253 std::memcpy(dst, src, size * sizeof(T));
254 return dst;
255}
256
257template <typename T>
258[[nodiscard]] T* copyArray(const T* src, size_t size, const UA_DataType& type) {
259 return IsPointerFree<T>::value
260 ? copyArray(src, size)
261 : copyArray(src, src + size, type).first; // NOLINT
262}
263
264} // namespace opcua::detail
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
UA_StatusCode UA_copy(const void *src, void *dst, const UA_DataType *type)
void UA_clear(void *p, const UA_DataType *type)
#define UA_free
#define UA_realloc
#define UA_calloc