Skip to content

Commit 2a1a5eb

Browse files
feat: Add compound assignment and increment/decrement operators for reactive variables (#18)
- Add type-safe operator overloads (++, --, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=) - Restrict operators to VarExpr types only using C++20 concepts - Maintain reactive notifications and batch operation compatibility - Add comprehensive unit tests (10 test cases, 100% pass rate) - Preserve all existing functionality (75/75 tests pass) Enables natural C++ syntax: auto v = var(1); v++; ++v; v += 1; Files modified: - include/reaction/core/concept.h: Added operator type safety concepts - include/reaction/core/react.h: Implemented operator overloads - test/unit/test_operators.cpp: Added comprehensive unit tests Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 11d6930 commit 2a1a5eb

File tree

3 files changed

+543
-0
lines changed

3 files changed

+543
-0
lines changed

include/reaction/core/concept.h

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,120 @@ concept ComparableType = requires(T &a, T &b) {
9595
{ a == b } -> std::convertible_to<bool>;
9696
};
9797

98+
// ==================== Operator Support Concepts ====================
99+
100+
/**
101+
* @brief Checks if a type supports pre-increment operation.
102+
*/
103+
template <typename T>
104+
concept PreIncrementable = requires(T &a) {
105+
{ ++a } -> std::convertible_to<T>;
106+
};
107+
108+
/**
109+
* @brief Checks if a type supports post-increment operation.
110+
*/
111+
template <typename T>
112+
concept PostIncrementable = requires(T &a) {
113+
{ a++ } -> std::convertible_to<T>;
114+
};
115+
116+
/**
117+
* @brief Checks if a type supports pre-decrement operation.
118+
*/
119+
template <typename T>
120+
concept PreDecrementable = requires(T &a) {
121+
{ --a } -> std::convertible_to<T>;
122+
};
123+
124+
/**
125+
* @brief Checks if a type supports post-decrement operation.
126+
*/
127+
template <typename T>
128+
concept PostDecrementable = requires(T &a) {
129+
{ a-- } -> std::convertible_to<T>;
130+
};
131+
132+
/**
133+
* @brief Checks if a type supports compound addition assignment.
134+
*/
135+
template <typename T, typename U = T>
136+
concept AddAssignable = requires(T &a, const U &b) {
137+
{ a += b } -> std::convertible_to<T&>;
138+
};
139+
140+
/**
141+
* @brief Checks if a type supports compound subtraction assignment.
142+
*/
143+
template <typename T, typename U = T>
144+
concept SubtractAssignable = requires(T &a, const U &b) {
145+
{ a -= b } -> std::convertible_to<T&>;
146+
};
147+
148+
/**
149+
* @brief Checks if a type supports compound multiplication assignment.
150+
*/
151+
template <typename T, typename U = T>
152+
concept MultiplyAssignable = requires(T &a, const U &b) {
153+
{ a *= b } -> std::convertible_to<T&>;
154+
};
155+
156+
/**
157+
* @brief Checks if a type supports compound division assignment.
158+
*/
159+
template <typename T, typename U = T>
160+
concept DivideAssignable = requires(T &a, const U &b) {
161+
{ a /= b } -> std::convertible_to<T&>;
162+
};
163+
164+
/**
165+
* @brief Checks if a type supports compound modulo assignment.
166+
*/
167+
template <typename T, typename U = T>
168+
concept ModuloAssignable = requires(T &a, const U &b) {
169+
{ a %= b } -> std::convertible_to<T&>;
170+
};
171+
172+
/**
173+
* @brief Checks if a type supports bitwise AND assignment.
174+
*/
175+
template <typename T, typename U = T>
176+
concept BitwiseAndAssignable = requires(T &a, const U &b) {
177+
{ a &= b } -> std::convertible_to<T&>;
178+
};
179+
180+
/**
181+
* @brief Checks if a type supports bitwise OR assignment.
182+
*/
183+
template <typename T, typename U = T>
184+
concept BitwiseOrAssignable = requires(T &a, const U &b) {
185+
{ a |= b } -> std::convertible_to<T&>;
186+
};
187+
188+
/**
189+
* @brief Checks if a type supports bitwise XOR assignment.
190+
*/
191+
template <typename T, typename U = T>
192+
concept BitwiseXorAssignable = requires(T &a, const U &b) {
193+
{ a ^= b } -> std::convertible_to<T&>;
194+
};
195+
196+
/**
197+
* @brief Checks if a type supports left shift assignment.
198+
*/
199+
template <typename T, typename U = T>
200+
concept LeftShiftAssignable = requires(T &a, const U &b) {
201+
{ a <<= b } -> std::convertible_to<T&>;
202+
};
203+
204+
/**
205+
* @brief Checks if a type supports right shift assignment.
206+
*/
207+
template <typename T, typename U = T>
208+
concept RightShiftAssignable = requires(T &a, const U &b) {
209+
{ a >>= b } -> std::convertible_to<T&>;
210+
};
211+
98212
/**
99213
* @brief Checks if a type implements the field interface.
100214
*/

include/reaction/core/react.h

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,152 @@ class React {
254254
return ObserverGraph::getInstance().getName(getPtr());
255255
}
256256

257+
// ==================== Compound Assignment Operators ====================
258+
259+
/// @brief Compound addition assignment operator (+=)
260+
template <typename U>
261+
requires(IsVarExpr<Expr> && !ConstType<Type> && AddAssignable<Type, U>)
262+
React &operator+=(const U &rhs) {
263+
auto currentVal = get();
264+
currentVal += rhs;
265+
value(std::move(currentVal));
266+
return *this;
267+
}
268+
269+
/// @brief Compound subtraction assignment operator (-=)
270+
template <typename U>
271+
requires(IsVarExpr<Expr> && !ConstType<Type> && SubtractAssignable<Type, U>)
272+
React &operator-=(const U &rhs) {
273+
auto currentVal = get();
274+
currentVal -= rhs;
275+
value(std::move(currentVal));
276+
return *this;
277+
}
278+
279+
/// @brief Compound multiplication assignment operator (*=)
280+
template <typename U>
281+
requires(IsVarExpr<Expr> && !ConstType<Type> && MultiplyAssignable<Type, U>)
282+
React &operator*=(const U &rhs) {
283+
auto currentVal = get();
284+
currentVal *= rhs;
285+
value(std::move(currentVal));
286+
return *this;
287+
}
288+
289+
/// @brief Compound division assignment operator (/=)
290+
template <typename U>
291+
requires(IsVarExpr<Expr> && !ConstType<Type> && DivideAssignable<Type, U>)
292+
React &operator/=(const U &rhs) {
293+
auto currentVal = get();
294+
currentVal /= rhs;
295+
value(std::move(currentVal));
296+
return *this;
297+
}
298+
299+
/// @brief Compound modulo assignment operator (%=)
300+
template <typename U>
301+
requires(IsVarExpr<Expr> && !ConstType<Type> && ModuloAssignable<Type, U>)
302+
React &operator%=(const U &rhs) {
303+
auto currentVal = get();
304+
currentVal %= rhs;
305+
value(std::move(currentVal));
306+
return *this;
307+
}
308+
309+
/// @brief Compound bitwise AND assignment operator (&=)
310+
template <typename U>
311+
requires(IsVarExpr<Expr> && !ConstType<Type> && BitwiseAndAssignable<Type, U>)
312+
React &operator&=(const U &rhs) {
313+
auto currentVal = get();
314+
currentVal &= rhs;
315+
value(std::move(currentVal));
316+
return *this;
317+
}
318+
319+
/// @brief Compound bitwise OR assignment operator (|=)
320+
template <typename U>
321+
requires(IsVarExpr<Expr> && !ConstType<Type> && BitwiseOrAssignable<Type, U>)
322+
React &operator|=(const U &rhs) {
323+
auto currentVal = get();
324+
currentVal |= rhs;
325+
value(std::move(currentVal));
326+
return *this;
327+
}
328+
329+
/// @brief Compound bitwise XOR assignment operator (^=)
330+
template <typename U>
331+
requires(IsVarExpr<Expr> && !ConstType<Type> && BitwiseXorAssignable<Type, U>)
332+
React &operator^=(const U &rhs) {
333+
auto currentVal = get();
334+
currentVal ^= rhs;
335+
value(std::move(currentVal));
336+
return *this;
337+
}
338+
339+
/// @brief Compound left shift assignment operator (<<=)
340+
template <typename U>
341+
requires(IsVarExpr<Expr> && !ConstType<Type> && LeftShiftAssignable<Type, U>)
342+
React &operator<<=(const U &rhs) {
343+
auto currentVal = get();
344+
currentVal <<= rhs;
345+
value(std::move(currentVal));
346+
return *this;
347+
}
348+
349+
/// @brief Compound right shift assignment operator (>>=)
350+
template <typename U>
351+
requires(IsVarExpr<Expr> && !ConstType<Type> && RightShiftAssignable<Type, U>)
352+
React &operator>>=(const U &rhs) {
353+
auto currentVal = get();
354+
currentVal >>= rhs;
355+
value(std::move(currentVal));
356+
return *this;
357+
}
358+
359+
// ==================== Increment/Decrement Operators ====================
360+
361+
/// @brief Pre-increment operator (++var)
362+
template <typename T = Type>
363+
requires(IsVarExpr<Expr> && !ConstType<Type> && PreIncrementable<T>)
364+
React &operator++() {
365+
auto currentVal = get();
366+
++currentVal;
367+
value(std::move(currentVal));
368+
return *this;
369+
}
370+
371+
/// @brief Post-increment operator (var++)
372+
template <typename T = Type>
373+
requires(IsVarExpr<Expr> && !ConstType<Type> && PostIncrementable<T>)
374+
Type operator++(int) {
375+
auto currentVal = get();
376+
auto oldVal = currentVal; // Store the old value
377+
currentVal++;
378+
value(std::move(currentVal));
379+
return oldVal; // Return the old value as Type, not React
380+
}
381+
382+
/// @brief Pre-decrement operator (--var)
383+
template <typename T = Type>
384+
requires(IsVarExpr<Expr> && !ConstType<Type> && PreDecrementable<T>)
385+
React &operator--() {
386+
auto currentVal = get();
387+
--currentVal;
388+
value(std::move(currentVal));
389+
return *this;
390+
}
391+
392+
/// @brief Post-decrement operator (var--)
393+
template <typename T = Type>
394+
requires(IsVarExpr<Expr> && !ConstType<Type> && PostDecrementable<T>)
395+
Type operator--(int) {
396+
auto currentVal = get();
397+
auto oldVal = currentVal; // Store the old value
398+
currentVal--;
399+
value(std::move(currentVal));
400+
return oldVal; // Return the old value as Type, not React
401+
}
402+
257403
/// @brief Get the internal shared pointer for advanced operations.
258404
[[nodiscard]] std::shared_ptr<react_type> getPtr() const {
259405
// Thread-safe access to weak_ptr using conditional mutex

0 commit comments

Comments
 (0)