Skip to content

Commit 2aababb

Browse files
committed
Docs: Add README
1 parent 6b86041 commit 2aababb

File tree

6 files changed

+308
-0
lines changed

6 files changed

+308
-0
lines changed

CITATION.cff

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cff-version: 1.2.0
2+
authors:
3+
- family-names: Chen
4+
given-names: Zhenshuo
5+
orcid: https://orcid.org/0000-0003-2091-4160
6+
title: Constrained Variable
7+
date-released: 2025-06-10
8+
url: https://github.com/Zhuagenborn/Constrained-Variable

README.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# Constrained Variable
2+
3+
![C++](docs/badges/C++.svg)
4+
[![CMake](docs/badges/Made-with-CMake.svg)](https://cmake.org)
5+
![GitHub Actions](docs/badges/Made-with-GitHub-Actions.svg)
6+
![License](docs/badges/License-MIT.svg)
7+
8+
## Introduction
9+
10+
A header-only library written in *C++23* for applying composable validation and transformation constraints to variables in a flexible and type-safe way, supporting:
11+
12+
- Range checks.
13+
- Null and emptiness checks.
14+
- Enumeration restrictions.
15+
- Value transformation.
16+
- Custom predicate validation.
17+
18+
Constraints are applied in a user-defined order and ensure the values are within the expected bounds or rules.
19+
Helpful error messages are generated when constraints are violated.
20+
21+
## Unit Tests
22+
23+
### Prerequisites
24+
25+
- Install *GoogleTest*.
26+
- Install *CMake*.
27+
28+
### Building
29+
30+
Go to the project folder and run:
31+
32+
```bash
33+
mkdir -p build
34+
cd build
35+
cmake -DCVAR_BUILD_TESTS=ON ..
36+
cmake --build .
37+
```
38+
39+
### Running
40+
41+
Go to the `build` folder and run:
42+
43+
```bash
44+
ctest -VV
45+
```
46+
47+
## Examples
48+
49+
See more examples in `tests/constrained_var_tests.cpp`.
50+
51+
```c++
52+
// Create a constraint.
53+
constexpr InRange<std::size_t> speed_range {{10, 80}};
54+
55+
// Create a constrained variable.
56+
InRangeVariable<std::size_t> speed {speed_range};
57+
58+
// Try to set a new value.
59+
speed.Set(input).transform_error([](const Error& err) noexcept {
60+
std::println("Failed to set the speed: {}", err.second);
61+
return false;
62+
});
63+
```
64+
65+
### Constraints
66+
67+
#### `Min`
68+
69+
`Min` ensures the value is not less than a specified minimum.
70+
71+
```c++
72+
constexpr Min<int> min_opt {10};
73+
MinVariable<int> var {min_opt};
74+
75+
EXPECT_FALSE(var.Set(5).has_value());
76+
EXPECT_EQ(var.Set(15).value_or(0), 15);
77+
```
78+
79+
#### `Max`
80+
81+
`Max` ensures the value does not exceed a specified maximum.
82+
83+
```c++
84+
constexpr Max<int> max_opt {100};
85+
MaxVariable<int> var {max_opt};
86+
87+
EXPECT_FALSE(var.Set(120).has_value());
88+
EXPECT_EQ(var.Set(80).value_or(0), 80);
89+
```
90+
91+
#### `InRange`
92+
93+
`InRange` ensures the value lies within a specified range.
94+
95+
```c++
96+
constexpr InRange<int> in_range_opt {{10, 100}};
97+
InRangeVariable<int> var {in_range_opt};
98+
99+
EXPECT_FALSE(var.Set(5).has_value());
100+
EXPECT_EQ(var.Set(50).value_or(0), 50);
101+
```
102+
103+
#### `NotInRange`
104+
105+
`NotInRange` ensures the value is not within a specified range.
106+
107+
```c++
108+
constexpr NotInRange<int> not_in_range_opt {{10, 100}};
109+
NotInRangeVariable<int> var {not_in_range_opt};
110+
111+
EXPECT_FALSE(var.Set(50).has_value());
112+
EXPECT_EQ(var.Set(5).value_or(0), 5);
113+
```
114+
115+
#### `InSet`
116+
117+
`InSet` ensures the value is in a specified set.
118+
119+
```c++
120+
const InSet<int> in_set_opt {1, 2};
121+
InSetVariable<int> var {in_set_opt};
122+
123+
EXPECT_EQ(var.Set(1).value_or(0), 1);
124+
EXPECT_EQ(var.Set(2).value_or(0), 2);
125+
EXPECT_FALSE(var.Set(3).has_value());
126+
```
127+
128+
#### `NotInSet`
129+
130+
`NotInSet` ensures the value is not in a specified set.
131+
132+
```c++
133+
const NotInSet<int> not_in_set_opt {1, 2};
134+
NotInSetVariable<int> var {not_in_set_opt};
135+
136+
EXPECT_FALSE(var.Set(1).has_value());
137+
EXPECT_FALSE(var.Set(2).has_value());
138+
EXPECT_EQ(var.Set(3).value_or(0), 3);
139+
```
140+
141+
#### `Clamp`
142+
143+
`Clamp` clamps the value into a specified range (`std::clamp`).
144+
145+
```c++
146+
constexpr Clamp<int> clamp_opt {{10, 100}};
147+
ClampVariable<int> var {clamp_opt};
148+
149+
EXPECT_EQ(var.Set(5).value_or(0), 10);
150+
EXPECT_EQ(var.Set(120).value_or(0), 100);
151+
```
152+
153+
#### `Enum`
154+
155+
`Enum` ensures the enumeration lies within an inclusive range.
156+
157+
```c++
158+
enum class Color { White, Red, Green, Black, Invalid };
159+
160+
template <>
161+
struct EnumValues<Color> {
162+
static constexpr std::array values {Color::White, Color::Red, Color::Green, Color::Black};
163+
};
164+
165+
EnumVariable<Color> var;
166+
167+
EXPECT_FALSE(var.Set(Color::Invalid).has_value());
168+
EXPECT_EQ(var.Set(Color::Red).value_or(Color::Invalid), Color::Red);
169+
```
170+
171+
#### `NotNull`
172+
173+
`NotNull` ensures the value is not null or `false`.
174+
175+
```c++
176+
NotNullVariable<int> var;
177+
178+
EXPECT_FALSE(var.Set(0).has_value());
179+
EXPECT_EQ(var.Set(42).value_or(0), 42);
180+
```
181+
182+
#### `NotEmpty`
183+
184+
`NotEmpty` ensures the container like `std::vector` is not empty.
185+
186+
```c++
187+
NotEmptyVariable<std::vector<int>> var;
188+
189+
EXPECT_FALSE(var.Set(std::vector<int> {}).has_value());
190+
EXPECT_EQ(var.Set(std::vector<int> {1}).value_or(std::vector<int> {}), std::vector<int> {1});
191+
```
192+
193+
#### `Predicate`
194+
195+
`Predicate` ensures the value satisfies a predicate.
196+
197+
```c++
198+
const Predicate<int> pred {[](const int x) noexcept {
199+
return x % 2 == 0;
200+
}};
201+
202+
PredicateVariable<int> var {pred};
203+
204+
EXPECT_FALSE(var.Set(3).has_value());
205+
EXPECT_EQ(var.Set(4).value_or(0), 4);
206+
```
207+
208+
#### `Transformer`
209+
210+
`Transformer` transforms the value before validation or storage, supporting chaining with other constraints.
211+
212+
```c++
213+
const Transformer<int, double> func {[](const int v) noexcept {
214+
return static_cast<double>(v);
215+
}};
216+
217+
TransformerVariable<int, double> var {func};
218+
219+
EXPECT_EQ(var.Set(5).value_or(0.0), 5.0);
220+
```
221+
222+
### Constraint Chains
223+
224+
This example transforms an integer to a vector as its size and then check if it is empty using a chain of `Transformer` and `NotEmpty`.
225+
226+
```c++
227+
ChainType<std::vector<int>> SizeToVector(const std::size_t& size) noexcept {
228+
return std::vector<int>(size);
229+
}
230+
231+
constexpr NotEmpty<std::vector<int>> not_empty_opt;
232+
const Transformer<std::size_t, std::vector<int>> transformer_opt {SizeToVector};
233+
ConstrainedVariable<std::vector<int>, decltype(transformer_opt), decltype(not_empty_opt)> var {transformer_opt, not_empty_opt};
234+
235+
EXPECT_FALSE(var.Set(0).has_value());
236+
EXPECT_EQ(var.Set(1).value_or(std::vector<int> {}), std::vector<int>(1));
237+
```
238+
239+
This example defines a boolean variable that is `true` only when the input value is `1` or `2` using a chain of `InSet` and `Transformer`.
240+
241+
```c++
242+
const InSet<int> in_set_opt {1, 2};
243+
const Transformer<ChainType<int>, bool> transformer_opt {
244+
[](const ChainType<int>& val) noexcept {
245+
return val.has_value();
246+
}
247+
};
248+
249+
ConstrainedVariable<bool, decltype(in_set_opt), decltype(transformer_opt)> var {in_set_opt, transformer_opt};
250+
251+
EXPECT_TRUE(var.Set(1).has_value());
252+
EXPECT_TRUE(var.Get());
253+
254+
EXPECT_TRUE(var.Set(3).has_value());
255+
EXPECT_FALSE(var.Get());
256+
```
257+
258+
In most cases, the parameter type of `Apply` is exactly the same as the return type of the previous constraint in the chain.
259+
For example, the return type of `Set<int>::Apply` and the parameter type of `Transformer<int, bool>::Apply` are both `ChainType<int>`.
260+
In this case, if the previous constraint returns an `std::unexpected`, the user-provided transformation function will not be called.
261+
262+
But currently we want the transformation function to return the validity of the previous constraint's return value.
263+
The constraint chain of `Set<int>` and `Transformer<int, bool>` does not work because the when the number is not in the set, the transformer will be skipped.
264+
Instead, we should use `Transformer<ChainType<int>, bool>`.
265+
The parameter type of its `Apply` is `ChainType<ChainType<int>>`.
266+
Regardless of whether `Set<int>` returns a number or an `std::unexpected`, the result will always be forwarded to the user-provided function.
267+
268+
### Validated Boolean Variables
269+
270+
You can directly use `ValidatedBoolVariable` if you need a boolean variable validated against a set of constraints.
271+
272+
```c++
273+
const InSet<int> in_set_opt {1, 2};
274+
ValidatedBoolVariable<int, decltype(in_set_opt)> var {in_set_opt};
275+
276+
EXPECT_TRUE(var.Set(1));
277+
EXPECT_TRUE(var.Set(2));
278+
EXPECT_FALSE(var.Set(3));
279+
```
280+
281+
### Validation
282+
283+
If you only need to validate values without storing them, you can directly `ConstraintChain` and `ValidationChain`.
284+
285+
```c++
286+
const InSet<int> in_set_opt {1, 2};
287+
ValidationChain<int, decltype(in_set_opt)> var {in_set_opt};
288+
289+
EXPECT_TRUE(var.Apply(1));
290+
EXPECT_TRUE(var.Apply(2));
291+
EXPECT_FALSE(var.Apply(3));
292+
```
293+
294+
## License
295+
296+
Distributed under the *MIT License*. See `LICENSE` for more information.

docs/badges/C++.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/badges/License-MIT.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/badges/Made-with-CMake.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)