A C++ library for type level flagging.
Flagged<T>
offers multiple wrapper types which allow you to add properties to your variables at type level. The wrapped types can still be used as the inner type, thanks to operator overloading.
#define FLAGGEDT_NO17 //before include in case c++17 isn't supported (types with c++17 tag not available then)
#include "flaggedT.h"
using namespace flaggedT;
The provided types can be created by using the constructor of the wanted type.
Some constructors will perform an action (e.g. Sorted
sorting its input data) or throw a FlaggedTError : std::logic_error
if the input is invalid (e.g. Positive(-1)
).
To ensure that the type reflects the state of the wrapped data, there's only immutable access to it. Both the moving and const reference conversion operator to T are overloaded. This makes it possible to move the wrapped data out, or to use it via const reference.
auto wrapped = NonNegative<int>(3); //if no exception is thrown, wrapped is now guarenteed >= 0
int abs = easy_abs(wrapped); //using a method defined for the wrapped type
int result = add_one(wrapped); //still able to use methods defined for the inner type
int unwrapped = std::move(wrapped);
//or
//int unwrapped = std::move(wrapped).unwrap();
int add_one(int in)
{
return in + 1;
}
int easy_abs(NonNegative<int> in)
{
return in.get();
}
Many of FlaggedT
's types can be converted between each other.
Correctness of those conversions are all enforced during compile time.
This allows for less restrictive functions to always be called by the parameters of more restrictive functions.
int conversion_inner(Positive<int> pi) {
return pi.get();
}
int conversion_outer() {
auto fi1 = FlooredInclusive<int, -1>(3); //could throw if passed number < -1
auto fi2 = FlooredInclusive<int, 1>(3); //could throw if passed number < 1
//below can't compile, since fi1 not guarantee to be Positive
//return conversion_inner(fi1); //"Positive can only be constructed by a FlooredInclusive if MIN > 0"
//below conversion from fi2 to Positive compiles and will never throw
return conversion_inner(fi2);
}
auto im = Immutable<int>(3); //can never be changed from now on
int copied = im.get(); //access to const& of wrapped data
int copied2 = im; //also implicit
int const& cref = im;
class MyClass {
shared_ptr<const OtherClass> data; //MyClass can't change data, but others might
int someCachedResult; //cached from data, will this be correct at all times?
}
class MyClass2 {
shared_im<OtherClass> data; //nobody can change this
int someCachedResult; //will stay correct, since data can't change
void foo() {
OtherClass const& cref = data.get(); //direct const access to inner
OtherClass const& cref2 = data; //implicit casting to inner type
}
}
auto wontCompile = NonNull<int*>(nullptr); //won't compile
int* i = nullptr;
auto throwsException = NonNull<int*>(std::move(i)); //exception
int* i2 = new int(3);
auto nn = NonNull<int*>(std::move(i2)); //works
//This is really useful for code which creates smart pointers
NonNull<shared_ptr<int>> generate() {
return NonNull<shared_ptr<int>>(make_shared(new int(3)));
}
//Methods using a NonNull won't have to check for nullptr anymore
void no_fear(NonNull<shared_ptr<int>> const& in) {
int x = *(in.get().get());
}
//creating any Sorted<T> will directly sort its data and keep it that way
auto alwaysSorted = Sorted<std::vector<int>>(std::vector<int>({4,9,2,13,15,17}));
//alwaysSorted.get() == {2,4,9,13,15,17}
int smallest(Sorted<std::vector<int>> const& sorted) {
//no need to check whether the input is already sorted
//or even sort it just to retrieve the smallest value
return sorted.get()[0]; //this might fail if sorted is empty, see NonEmpty
}
//creating any Unqique<T> will directly make its data unique and keep it that way
auto alwaysUnique = Unique<std::vector<int>>(std::vector<int>({1,1,2,2,3,3}));
//alwaysUnique.get == {1,2,3}
void algorithm_not_allowing_duplicate_data(Unique<std::vector<int>> const& unique) {
//no need to check, already ensured by its type
}
//always unique and sorted
//data is randomly shuffled
auto zero = 0;
auto nz = NonZero<int>(std::move(zero))); //EXCEPTION
auto i = 2;
auto nz2 = NonZero<int>(i); //works just fine
//Division by zero? No problem!
void safe_div(int nominator, NonZero<int> const& denominator) {
return nominator / denominator.get();
}
//T > 0
//T <= 0
//T < 0
//T >= 0
//T <= MAX
//T < MAX
//T >= MIN
//T > MIN
//T >= MIN && T <= MAX
//T > MIN && T < MAX
auto emptyVec = std::vector<int>();
auto ne = NonEmpty<std::vector<int>>(std::move(emptyVec))); //EXCEPTION
auto vec = std::vector<int>({1,2,3});
auto ne2 = NonEmpty<std::vector<int>>(std::move(vec))); //works just fine
//Methods now can access the first element without checking the size
void access_first(NonEmpty<std::vector<int>> const& in) {
auto first = in.get()[0]; //this will always work
}
auto tooSmall = std::vector<int>({1,2,3});
auto throwsException = MoreThan<std::vector<int>,3>(std::move(tooSmall)); //Exception
auto bigEnough = std::vector<int>({1,2,3,4});
auto works = MoreThan<std::vector<int>,3>(std::move(bigEnough));
//Methods now can access the first SIZE-elements without checking the size
void access_four(MoreThan<std::vector<int>,3> const& in) {
auto first = in.get()[0]; //this will always work
auto second = in.get()[1]; //this will always work
auto third = in.get()[2]; //this will always work
auto fourth = in.get()[3]; //this will always work
}
//analog to MoreThan
//T.size() < SIZE
//analog to MoreThan and LessThan, but enforcing an exact size
//T.size() == SIZE
//analog to MoreThan and LessThan, but enforcing a size range
//T.size() >= MINSIZE && T.size() <= MAXSIZE
//ensures that all passed types have the same size
//its internal type is std::tuple<Types...>
auto works = EqualSized<vector<int>, array<int, 3>, array<double, 3>>({ 1, 2, 3 }, { 5, 6, 7 }, { 1.0, 2.7, 1.1 });
auto fails = EqualSized<vector<int>, array<int, 3>, array<double, 3>>({ 1, 2, 3, 4 }, { 5, 6, 7 }, { 1.0, 2.7, 1.1 });
auto fails2 = EqualSized<vector<int>, array<int, 3>, array<double, 2>>({ 1, 2, 3 }, { 5, 6, 7 }, { 1.0, 2.7 });
auto fromTuple = EqualSized(make_tuple<vector<int>, array<int, 3>, array<double, 3>>({ 1, 2, 3, 4 }, { 5, 6, 7 }, { 1.0, 2.7, 1.1 }));
4.0.0
MIT (See LICENSE)