UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
GuardedInt.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "CoreTypes.h"
6#include "Concepts/Integral.h"
7#include "HAL/PlatformMath.h"
10#include <limits>
11#include <type_traits>
12
54template<UE::CIntegral IntType>
56{
57private:
58 static_assert(std::is_integral_v<IntType>, "Only defined for integer types");
59 typedef typename std::make_unsigned_t<IntType> UnsignedType;
60
61 static constexpr IntType MinValue = std::numeric_limits<IntType>::min();
62 static constexpr IntType MaxValue = std::numeric_limits<IntType>::max();
63 static constexpr IntType NumBits = IntType((sizeof(IntType) / sizeof(char)) * 8); // Using sizeof to guess the bit count, ugh.
64
65 IntType Value = 0;
66 bool bIsValid = false;
67
68public:
70 [[nodiscard]] TGuardedInt() = default;
71
73 template<UE::CIntegral ValueType>
74 [[nodiscard]] explicit TGuardedInt(ValueType InValue)
75 : Value((IntType)InValue), bIsValid(false)
76 {
77 if constexpr (std::is_signed_v<ValueType>)
78 {
79 bIsValid = (InValue >= MinValue && InValue <= MaxValue);
80 }
81 else
82 {
83 bIsValid = InValue <= UnsignedType(MaxValue);
84 }
85 }
86
88 [[nodiscard]] TGuardedInt(const TGuardedInt& Other) = default;
89
92
94 [[nodiscard]] bool IsValid() const
95 {
96 return bIsValid;
97 }
98
100 [[nodiscard]] IntType Get(const IntType DefaultValue) const
101 {
102 return IsValid() ? Value : DefaultValue;
103 }
104
108 [[nodiscard]] IntType GetChecked(const IntType DefaultValue = 0) const
109 {
110 checkf(IsValid(), TEXT("Invalid value in TGuardedInt::GetChecked."));
111 return Get(DefaultValue);
112 }
113
116 {
117 if (bIsValid != Other.bIsValid)
118 {
119 return false;
120 }
121
122 return !bIsValid || Value == Other.Value;
123 }
124
127 {
128 if (bIsValid != Other.bIsValid)
129 {
130 return true;
131 }
132
133 return bIsValid && Value != Other.Value;
134 }
135
136 // There are intentionally no overloads for the ordered comparison operators, because we have
137 // to decide what to do about validity as well. Instead, do this.
138
141 {
142 return bIsValid && Other.bIsValid;
143 }
144
146 template<typename ValueType>
147 [[nodiscard]] bool ValidAndLessThan(const ValueType Other) const
148 {
150 return ComparisonValid(CheckedOther) && Value < CheckedOther.Value;
151 }
152
154 template<typename ValueType>
155 [[nodiscard]] bool ValidAndLessOrEqual(const ValueType Other) const
156 {
158 return ComparisonValid(CheckedOther) && Value <= CheckedOther.Value;
159 }
160
162 template<typename ValueType>
163 [[nodiscard]] bool ValidAndGreaterThan(const ValueType Other) const
164 {
166 return ComparisonValid(CheckedOther) && Value > CheckedOther.Value;
167 }
168
170 template<typename ValueType>
171 [[nodiscard]] bool ValidAndGreaterOrEqual(const ValueType Other) const
172 {
174 return ComparisonValid(CheckedOther) && Value >= CheckedOther.Value;
175 }
176
178 template<typename ValueType>
179 [[nodiscard]] bool InvalidOrLessThan(const ValueType Other) const
180 {
182 return !ComparisonValid(CheckedOther) || Value < CheckedOther.Value;
183 }
184
186 template<typename ValueType>
187 [[nodiscard]] bool InvalidOrLessOrEqual(const ValueType Other) const
188 {
190 return !ComparisonValid(CheckedOther) || Value <= CheckedOther.Value;
191 }
192
194 template<typename ValueType>
195 [[nodiscard]] bool InvalidOrGreaterThan(const ValueType Other) const
196 {
198 return !ComparisonValid(CheckedOther) || Value > CheckedOther.Value;
199 }
200
202 template<typename ValueType>
203 [[nodiscard]] bool InvalidOrGreaterOrEqual(const ValueType Other) const
204 {
206 return !ComparisonValid(CheckedOther) || Value >= CheckedOther.Value;
207 }
208
209 // Arithmetic operations
210
213 {
214 if constexpr (std::is_signed_v<IntType>)
215 {
216 // Unary negation (for two's complement) overflows iff the operand is MinValue.
217 return (bIsValid && Value > MinValue) ? TGuardedInt(-Value) : TGuardedInt();
218 }
219 else
220 {
221 // Negating anything but zero overflows unsigned integers.
222 return (bIsValid && Value == 0) ? TGuardedInt(-Value) : TGuardedInt();
223 }
224 }
225
228 {
229 // Any sum involving an invalid value is invalid.
230 if (!bIsValid || !Other.bIsValid)
231 {
232 return TGuardedInt();
233 }
234
235 IntType Result;
236 return FPlatformMath::AddAndCheckForOverflow<IntType>(Value, Other.Value, Result)
237 ? TGuardedInt(Result)
238 : TGuardedInt();
239 }
240
243 {
244 // Any difference involving an invalid value is invalid.
245 if (!bIsValid || !Other.bIsValid)
246 {
247 return TGuardedInt();
248 }
249
250 IntType Result;
251 return FPlatformMath::SubtractAndCheckForOverflow<IntType>(Value, Other.Value, Result)
252 ? TGuardedInt(Result)
253 : TGuardedInt();
254 }
255
258 {
259 // Any product involving invalid values is invalid.
260 if (!bIsValid || !Other.bIsValid)
261 {
262 return TGuardedInt();
263 }
264
265 IntType Result;
266 return FPlatformMath::MultiplyAndCheckForOverflow<IntType>(Value, Other.Value, Result)
267 ? TGuardedInt(Result)
268 : TGuardedInt();
269 }
270
273 {
274 // Any quotient involving invalid values is invalid.
275 if (!bIsValid || !Other.bIsValid)
276 {
277 return TGuardedInt();
278 }
279
280 // Luckily for us, division generally makes things smaller, so there's only two things to watch
281 // out for: division by zero is not allowed, and division of MinValue by -1 would give -MinValue
282 // which overflows. All other combinations are fine.
283 if (Other.Value == 0 || (std::is_signed_v<IntType> && Value == MinValue && Other.Value == -1))
284 {
285 return TGuardedInt();
286 }
287
288 return TGuardedInt(Value / Other.Value);
289 }
290
293 {
294 // Any quotient involving invalid values is invalid.
295 if (!bIsValid || !Other.bIsValid)
296 {
297 return TGuardedInt();
298 }
299
300 // Same error cases as for division.
301 if (Other.Value == 0 || (std::is_signed_v<IntType> && Value == MinValue && Other.Value == -1))
302 {
303 return TGuardedInt();
304 }
305
306 return TGuardedInt(Value % Other.Value);
307 }
308
311 {
312 // Any shift involving invalid values is invalid.
313 if (!bIsValid || !Other.bIsValid)
314 {
315 return TGuardedInt();
316 }
317
318 // Left-shifts by negative values or >= the width of the type are always invalid.
319 if (Other.Value < 0 || Other.Value >= NumBits)
320 {
321 return TGuardedInt();
322 }
323
324 if constexpr (std::is_signed_v<IntType>)
325 {
326 const int ShiftAmount = Other.Value;
327
328 // Once again, taking our overflow-prone expression and using algebra to find
329 // a form that doesn't overflow:
330 //
331 // MinValue <= a * 2^b <= MaxValue
332 // <=> MinValue * 2^(-b) <= a <= MaxValue * 2^(-b)
333 //
334 // The LHS is exact because MinValue is -2^(NumBits - 1) for two's complement,
335 // and we just ensured that 0 <= b < NumBits (with b integer).
336 //
337 // The RHS has a fractional part whereas a is integer; therefore, we can
338 // substitute floor(MaxValue * 2^(-b)) for the RHS without changing the result.
339 //
340 // And that gives us our test!
341 return ((MinValue >> ShiftAmount) <= Value && Value <= (MaxValue >> ShiftAmount)) ? TGuardedInt(Value << ShiftAmount) : TGuardedInt();
342 }
343 else
344 {
345 return (static_cast<IntType>(Value << Other.Value) >> Other.Value) == Value
346 ? TGuardedInt(static_cast<IntType>(Value << Other.Value))
347 : TGuardedInt();
348 }
349 }
350
353 {
354 // Any shift involving invalid values is invalid.
355 if (!bIsValid || !Other.bIsValid)
356 {
357 return TGuardedInt();
358 }
359
360 // Right-shifts by negative values or >= the width of the type are always invalid.
361 if (Other.Value < 0 || Other.Value >= NumBits)
362 {
363 return TGuardedInt();
364 }
365
366 // Right-shifts don't have any overflow conditions, so we're good!
367 return TGuardedInt(Value >> Other.Value);
368 }
369
372 {
373 if (!bIsValid)
374 {
375 return TGuardedInt();
376 }
377
378 if constexpr (std::is_signed_v<IntType>)
379 {
380 // Note the absolute value of MinValue overflows, so this is not completely trivial.
381 // Can't just use TGuardedInt(abs(Value)) here!
382 return (Value < 0) ? -*this : *this;
383 }
384 else
385 {
386 // Abs on unsigned integers is the identity function, and can't overflow.
387 return TGuardedInt(Value);
388 }
389 }
390
391 // Mixed-type operators and assignment operators reduce to the base operators systematically
392#define UE_GUARDED_SIGNED_INT_IMPL_BINARY_OPERATOR(OP) \
393 /* Mixed-type expressions that coerce both operands to guarded ints */ \
394 TGuardedInt operator OP(IntType InB) const { return *this OP TGuardedInt(InB); } \
395 friend TGuardedInt operator OP(IntType InA, TGuardedInt InB) { return TGuardedInt(InA) OP InB; } \
396 /* Assignment operators, direct and mixed */ \
397 TGuardedInt& operator OP##=(TGuardedInt InB) { return *this = *this OP InB; } \
398 TGuardedInt& operator OP##=(IntType InB) { return *this = *this OP TGuardedInt(InB); } \
399 /* end */
400
408
409#undef UE_GUARDED_SIGNED_INT_IMPL_BINARY_OPERATOR
410};
411
413template<typename SignedType>
415
418
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
#define TEXT(x)
Definition Platform.h:1272
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define UE_GUARDED_SIGNED_INT_IMPL_BINARY_OPERATOR(OP)
Definition GuardedInt.h:392
Definition GuardedInt.h:56
TGuardedInt operator+(const TGuardedInt Other) const
Definition GuardedInt.h:227
bool operator!=(const TGuardedInt Other) const
Definition GuardedInt.h:126
bool InvalidOrLessOrEqual(const ValueType Other) const
Definition GuardedInt.h:187
bool ValidAndLessOrEqual(const ValueType Other) const
Definition GuardedInt.h:155
bool InvalidOrGreaterOrEqual(const ValueType Other) const
Definition GuardedInt.h:203
TGuardedInt operator>>(const TGuardedInt Other) const
Definition GuardedInt.h:352
bool ValidAndGreaterOrEqual(const ValueType Other) const
Definition GuardedInt.h:171
TGuardedInt operator/(const TGuardedInt Other) const
Definition GuardedInt.h:272
TGuardedInt(const TGuardedInt &Other)=default
TGuardedInt Abs() const
Definition GuardedInt.h:371
TGuardedInt()=default
TGuardedInt(ValueType InValue)
Definition GuardedInt.h:74
bool operator==(const TGuardedInt Other) const
Definition GuardedInt.h:115
TGuardedInt operator-() const
Definition GuardedInt.h:212
bool InvalidOrGreaterThan(const ValueType Other) const
Definition GuardedInt.h:195
bool IsValid() const
Definition GuardedInt.h:94
bool ValidAndLessThan(const ValueType Other) const
Definition GuardedInt.h:147
TGuardedInt operator*(const TGuardedInt Other) const
Definition GuardedInt.h:257
TGuardedInt operator<<(const TGuardedInt Other) const
Definition GuardedInt.h:310
bool ComparisonValid(const TGuardedInt Other) const
Definition GuardedInt.h:140
TGuardedInt & operator=(const TGuardedInt &Other)=default
bool InvalidOrLessThan(const ValueType Other) const
Definition GuardedInt.h:179
IntType Get(const IntType DefaultValue) const
Definition GuardedInt.h:100
bool ValidAndGreaterThan(const ValueType Other) const
Definition GuardedInt.h:163
IntType GetChecked(const IntType DefaultValue=0) const
Definition GuardedInt.h:108
TGuardedInt operator%(const TGuardedInt Other) const
Definition GuardedInt.h:292
@ false
Definition radaudio_common.h:23