Skip to content

I3ck/FlaggedT

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FlaggedT

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.

Examples, Tutorials

#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;

Creating wrapped types

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)).

Accessing the wrapped data

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();
}

Safe conversion between wrapped types

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);
}

Immutable<T>

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;

shared_im<T>

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
    }
}

NonNull<T>

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());
}

Sorted<T>

//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
}

Unique<T>

//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
}

UniqueAndSorted<T>

//always unique and sorted

Shuffled<T>

//data is randomly shuffled

NonZero<T>

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();
}

Positive<T> : NonZero<T>

//T > 0

NonPositive<T>

//T <= 0

Negative<T> : NonZero<T>

//T < 0

NonNegative<T>

//T >= 0

CeiledInclusive<T, T MAX>

//T <= MAX

CeiledExclusive<T, T MAX>

//T < MAX

FlooredInclusive<T, T MIN>

//T >= MIN

FlooredExclusive<T, T MIN>

//T > MIN

BoundedInclusive<T, T MIN, T MAX>

//T >= MIN && T <= MAX

BoundedExclusive<T, T MIN, T MAX>

//T > MIN && T < MAX

NonEmpty<T>

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
}

MoreThan<T,SIZE> : NonEmpty<T>

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
}

LessThan<T,SIZE>

//analog to MoreThan
//T.size() < SIZE

FixedSized<T,SIZE>

//analog to MoreThan and LessThan, but enforcing an exact size
//T.size() == SIZE

FixedRangeInclusive<T,MINSIZE,MAXSIZE>

//analog to MoreThan and LessThan, but enforcing a size range
//T.size() >= MINSIZE && T.size() <= MAXSIZE

EqualSized<Types...> [c++17]

//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 }));

Version

4.0.0

License

MIT (See LICENSE)

About

A C++ library for type level flagging

Resources

License

Stars

Watchers

Forks

Packages

No packages published