/* *** option_ptr monadic smart pointer *** An `option_ptr` works like `std::unique_ptr` but does not by default overload dereferencing operations such as * and ->, although these are still available under conditional compilation. The idea is to prevent problems caused by dereferencing a unique_pointer after a move. The heap value pointed to by an option_ptr should only be accessed through combinators such as bind/map/match. Like unique_ptr, option_ptr always points to heap. It is not intended as a replacement for the built-in std::optional type. Unlike unique_ptr, the constructor that take a raw pointer is private and can only be invoked from friend functions `Some` and `Nothing`. Some is similar to make_unique. The move semantics of option_ptr is similar to that of unique_ptr. A specialization for arrays is also defined. The unchecked pointer operators * and -> become available if source is compiled under the `g++ -D UNCHECKED_DEREF` option. The included sample program `bst4.cpp` provides an implementation of binary search trees using option_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, const option_ptr& r) { if (r.ptr) { out << "Some(" << *r.ptr << ")"; } else { out << "None"; } return out; } // one-time constant for this type static const option_ptr None; /////////////// Move Semantics: template option_ptr& operator=(option_ptr&& other) { if (this == (option_ptr*)&other) return *this; if (ptr) deleter::destruct(ptr); // prevent memory leaks before assignment ptr = static_cast(other.ptr); // takes over "ownership" of heap value other.ptr = nullptr; // ownership is unqiue return *this; }// move assignment operator template option_ptr(option_ptr&& other) { if (ptr) deleter::destruct(ptr); ptr = static_cast(other.ptr); other.ptr = nullptr; }// move constructor const option_ptr& operator =(const option_ptr& other) = delete; option_ptr(const option_ptr& other) = delete; ///////////////// 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 #ifdef UNCHECKED_DEREF TY& operator *() { return *ptr; } TY* operator ->() { return ptr; } #endif // 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); #ifdef UNCHECKED_DEREF cout << *num2 << " :: (unchecked deref)\n"; #endif num2.memcpy_from(num2,sizeof(int)); cout << "num2: " << num2 << endl; return 0; }//main */