/* *** option_ptr monadic smart pointer *** An option_ptr works like unique_ptr but does not overload dereference operations such as * and ->. A unique_ptr can be dereferenced after a move, resulting in a null pointer error. Such errors are avoided by option_ptr because the value pointed to can only be accessed through combinators such as bind/map/match. Like unique_ptr, option_ptr always points to heap and should only be created with the Some and Nothing functions. Some is like make_unique. The move semantics of option_ptr is the same as for unique_ptr. */ #include #include #include #include using namespace std; template struct pointer_deleter { static void destruct(T* p) { delete p; } }; template struct pointer_deleter { static void destruct(T* p) { delete[] p; } }; template> class option_ptr { protected: TY* ptr; // internal pointer to something of type TY //template void connect(option_ptr& R, TY* pr) { // called from subclass R.ptr = pr; } private: option_ptr(TY* p) {ptr=p;} // private constructors discourage raw pointers public: constexpr option_ptr(): ptr{nullptr} {} // null is only used internally ~option_ptr() { if (ptr) deleter::destruct(ptr); } // destructor void drop() { if (ptr) deleter::destruct(ptr); ptr=nullptr; } operator bool() const { return ptr!=nullptr; } // overload bool operator //template //friend class option_ptr; // friend functions that are part of API template friend option_ptr Some(Args&&... args); template friend option_ptr Nothing(); friend ostream& operator <<(ostream& out, option_ptr&& r) { if (r.ptr) { out << "Some(" << *r.ptr << ")"; } else { out << "None"; } return out; }// overload << for R-value references friend ostream& operator <<(ostream& out, option_ptr& r) { out << move(r); // this move does not invoke move constructor return out; }// overload << for L-value references // one-time constant for this type static const option_ptr None; /////////////// Move Semantics: option_ptr& operator=(option_ptr&& other) { if (ptr) deleter::destruct(ptr); // prevent memory leaks before assignment ptr = other.ptr; // takes over "ownership" of heap value other.ptr = nullptr; // ownership is unqiue return *this; }// move assignment operator option_ptr(option_ptr&& other) { if (ptr) deleter::destruct(ptr); ptr = other.ptr; other.ptr = nullptr; }// move constructor ///////////////// Monadic operations without move: template option_ptr bind(function(TY&)> f) { if (ptr) return f(*ptr); else return option_ptr(); //else return move(*(option_ptr*)this); // not a good idea }//bind template option_ptr map(function f) { if (ptr) { TU result = f(*ptr); TU* pr = new TU(result); return option_ptr(pr); } else return option_ptr(); }// a.map(f) == a.bind([&](auto x){return Some(f(x));}) void map_do(function f) { if (ptr) f(*ptr); } template TU match(function somefun, function nonefun) { if (ptr) return somefun(*ptr); else return nonefun(); }//match // explicit template instantiation - only in namespace scope //template <> bool match(function, function); void match_do(function some, function none) { if (ptr) some(*ptr); else none(); }//match do // get_or does not move, returns reference TY& get_or(TY& default_val) { if (ptr) return *ptr; else return default_val; } // map with function returning same type option_ptr& mutate(function f) { if (ptr) { *ptr = f(*ptr); } return *this; }//map ////////////// Monadic operations with move: TY take_or(TY default_val) { if (ptr) { TY x = move(*ptr); deleter::destruct(ptr); ptr = nullptr; return x; } else return default_val; }//take // map_move moves value into new option_ptr template option_ptr map_move(function f) { if (ptr) { TU result = f(*ptr); TU* pr = new TU(result); deleter::destruct(ptr); ptr = nullptr; return option_ptr(pr); } else return option_ptr(); }//map_move // Special operations - use with care void memset_with(int c, size_t n) { if (ptr) memset(ptr,c,n); } void memcpy_from(option_ptr& other, size_t n) { if (ptr && other.ptr) memcpy(ptr,other.ptr,n); } }; // option_ptr class // Some: (monadic unit), works like make_unique template option_ptr Some(Args&&... args) { return option_ptr(new TY(std::forward(args)...)); // forward retains original l/r-value status of args }// Some template option_ptr Nothing() { return option_ptr(); }//None template constexpr option_ptr None = move(option_ptr()); //////////////////////////////////////////////////////////////////////////// ///////////////// Specialization for Arrays, /* For the array verion of option_ptr, we deviated from the implementation of unique_ptr even more, by treating the array as a unqiue monad with its own operations including map/reduce. However, the [i] operator is overloaded in the same way, with no runtime overhead to check for the validity of i, and so it can be used unsafely. */ template class option_ptr : public option_ptr> { private: unsigned int len; option_ptr(unsigned int n): len{n} { this->ptr = new TY[n]; } public: option_ptr(): len{0} { this->ptr = nullptr; } //~option_ptr() { if (this->ptr) delete[] this->ptr; } template friend option_ptr Some_array(unsigned int n); TY& operator [] (unsigned int i) { return this->ptr[i]; } // unchecked option_ptr operator() (unsigned int i) { // checked if (this->ptr && (i(this->ptr[i]); //copy! // option_ptr R; // this->connect(R,this->ptr+i); // return R; //} return Nothing(); } unsigned int size() { return len; } // monadic operations specific to arrays: option_ptr& foreach(function fun) { for(int i=0;iptr[i]); return *this; } template option_ptr Map(function f) { if (!this->ptr) return Nothing(); option_ptr R(len); for(int i=0;iptr[i]); return R; }//map // apply left-associative function with default value id TY reduce(function f, TY id) { if (len==0) return id; TY ax = move(this->ptr[0]); for(int i=1;iptr[i]); // no move on R-value return ax; }//reduce template option_ptr Map_move(function f) { if (!this->ptr) return Nothing(); option_ptr R(len); for(int i=0;iptr[i])); deleter::destruct(this->ptr); len = 0; return R; }//map option_ptr& reverse() { for(int i=0;iptr[i],this->ptr[len-1-i]); return *this; }//reverse bool swap(unsigned int i, unsigned int k) { if (iptr[i], this->ptr[k]); return true; } else return false; }//swap bool set(unsigned int i, TY x) { // checked set if (iptr[i] = move(x); return true; } else return false; } // returns index of first occurrence of x, or .size() if not found unsigned int find(TY& x) { for(unsigned int i=0;iptr[i]) return i; return len; } option_ptr join(option_ptr other) { // with move unsigned int n = len + other.size(); if (n==0) return Nothing(); option_ptr J(n); for(auto i=0;iptr[i]); for(auto i=len;i& operator=(option_ptr&& other) { if (this->ptr) deleter::destruct(this->ptr); this->ptr = other.ptr; len = other.len; other.ptr = nullptr; other.len = 0; return *this; }// move assignment operator option_ptr(option_ptr&& other) { if (this->ptr) deleter::destruct(this->ptr); this->ptr = other.ptr; len = other.len; other.ptr = nullptr; other.len = 0; }// move constructor };//option_ptr template option_ptr Some_array(unsigned int n) { return option_ptr(n); } /* DEMO void arraydemo() { option_ptr C = Some_array(10); option_ptr A = Some_array(20); A = move(C); for(int i=0;i<10;i++) A[i] = i*i; for(int i=0;i B = move(A); A(4).map_do([](int& x) { cout << "A got " << x << endl; }); B(4).map_do([](int& x) { cout << "B got " << x << endl; }); option_ptr B2 = B.Map([](int& x) {return x*10;}); for(int i=0;i[]> D = Some_array>(2); D[1] = Some(55); //D(1).map_do([](auto& x){}); // won't compile: requires std::copyable cout << "\nend of arraydemo\n"; } //some functions that return option_ptr option_ptr safediv(double x, int y) { if (y==0) { cerr << "ERROR: division of " << x << " by zero\n"; return Nothing(); } else return Some(x/y); }//safediv option_ptr parseint(string n) { //convert stoi to locally catch exception option_ptr answer = Nothing(); try { answer = Some(stoi(n)); } catch(const invalid_argument& ia) { cerr << "ERROR: "<< n << " cannot be parsed to an integer\n"; } return answer; }//parseint option_ptr largest(int A[], int length) { if (length<1) return Nothing(); int ax = A[0]; for(int i=1;iax) ax=A[i]; } return Some(ax); }//largest //// for testing: int main(int argc, char* argv[]) { arraydemo(); option_ptr input; option_ptr number; if (argc>1) input = parseint(argv[1]); // no move required for R-values number = move(input); cout << "input: " << input << endl; cout << "moved to number: " << number << endl; number .mutate([](auto& x){return x-5;}) .map([](int& x)->int {return x-5;}) .bind([](int& x)->option_ptr {return safediv(100,x);}) .mutate([](auto& x){return x*x;}) .match_do( [](auto& x){cout<<"result="<(50); int x = number.match([](int& y){return y/2;}, [](){return 0;}); cout << "x is " << x << endl; bool odd = number.match([](int& y){return y%2==1;},[](){return false;}); cout << "odd: " << odd << endl; int A[5] = {3,2,7,4,1}; cout << "largest: " << largest(A,5) << endl; cout << "largest: " << largest(A,0) << endl; auto num2 = Some(3); num2.memcpy_from(num2,sizeof(int)); cout << "num2: " << num2 << endl; return 0; }//main */ /* MIT License Copyright (c) 2023 Chuck Liang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */