├── .buckconfig ├── .gitignore ├── .gitmodules ├── BUCK ├── Channel ├── BUCK ├── Channel.hpp ├── Channel.ipp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Concurrent ├── BUCK ├── Concurrent.hpp ├── Concurrent.ipp ├── Concurrent.pre.hpp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Defer ├── BUCK ├── Defer.hpp ├── Defer.ipp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Enumerate ├── Emnumerate.hpp └── Readme.md ├── Executor ├── BUCK ├── Executor.cpp ├── Executor.hpp └── InlineExecutor.hpp ├── ForEach ├── BUCK ├── ForEach.hpp ├── ForEach.ipp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Functional ├── BUCK ├── Functional.hpp ├── detail │ └── Function.hpp └── test │ ├── BUCK │ └── test.cpp ├── Future ├── BUCK ├── Future.hpp ├── Future.ipp ├── FutureError.cpp ├── FutureError.hpp ├── Promise.hpp ├── Promise.ipp ├── README.md ├── SharedFuture.hpp ├── SharedFuture.ipp ├── detail │ ├── Future-pre.hpp │ ├── FutureImpl.hpp │ └── FutureImpl.ipp └── test │ ├── BUCK │ └── test.cpp ├── Invoke ├── Invoke.hpp ├── Invoke.ipp ├── README.md └── something.cpp ├── MoveInto ├── BUCK ├── MoveInto.hpp ├── MoveInto.ipp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Mutable ├── BUCK ├── Mutable.hpp ├── Mutable.pre.hpp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Mutex └── README.md ├── Optional ├── BUCK ├── Optional.hpp └── README.md ├── OrderedContainer ├── BUCK ├── OrderedContainer.hpp ├── OrderedContainer.ipp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Overload ├── BUCK ├── Overload.hpp ├── Overload.ipp ├── README.md └── test │ ├── BUCK │ └── test.cpp ├── Portability ├── BUCK ├── cpp17.hpp └── detail │ └── optional.hpp ├── Proxy └── Proxy.hpp ├── README.md ├── Range ├── BUCK ├── README.md ├── Range.hpp ├── Range.ipp └── test │ ├── BUCK │ └── test.cpp ├── Recursive ├── BUCK ├── Recursive.hpp ├── Recursive.ipp └── test │ ├── BUCK │ └── test.cpp ├── Settings └── README.md ├── Singleton ├── BUCK ├── README.md ├── Singleton.cpp ├── Singleton.hpp ├── Singleton.ipp └── test │ ├── BUCK │ └── test.cpp ├── Tags ├── BUCK ├── README.md ├── Tags.hpp └── test │ ├── BUCK │ └── test.cpp ├── Threads ├── BUCK ├── README.md ├── RecursiveMutex.cpp ├── RecursiveMutex.hpp ├── ThreadTest.cpp ├── ThreadTest.hpp ├── Threads.hpp ├── UniqueLock.hpp ├── UniqueLock.ipp └── test │ ├── BUCK │ ├── UniqueLockTest.cpp │ └── test.cpp ├── Traits ├── BUCK ├── README.md ├── Traits.hpp ├── detail │ ├── Algorithm.hpp │ ├── Conjunction.hpp │ ├── FunctionIntrospect.hpp │ ├── Functional.hpp │ ├── IsCallable.hpp │ ├── IsInstantiationOf.hpp │ ├── IsOneOf.hpp │ ├── TypeLists.hpp │ ├── UnwrapPair.hpp │ ├── Utility.hpp │ └── VoidT.hpp └── test │ ├── BUCK │ └── test.cpp ├── TransparentList ├── BUCK ├── TransparentList.hpp ├── TransparentList.ipp └── test │ ├── BUCK │ └── test.cpp ├── Try ├── BUCK ├── README.md ├── Try-pre.hpp ├── Try.hpp ├── Try.ipp └── test │ ├── BUCK │ └── test.cpp ├── TypeSet ├── BUCK ├── README.md ├── TypeSet.hpp ├── TypeSet.ipp ├── detail │ └── TypeSet-pre.hpp └── test │ ├── BUCK │ └── test.cpp ├── Utility ├── BUCK ├── README.md ├── Utility.hpp ├── Utility.ipp └── test │ ├── BUCK │ └── test.cpp ├── Variant ├── BUCK └── README.md ├── external ├── boost │ ├── .buckconfig │ ├── BUCK │ └── build.sh └── gtest │ └── BUCK └── mode ├── asan ├── debug ├── tsan └── ubsan /.buckconfig: -------------------------------------------------------------------------------- 1 | [cxx] 2 | gtest_dep = //external/gtest:gtest 3 | cxxflags = -std=c++14 -Wall -Werror -Wvla -Wextra -pedantic -O3 4 | 5 | [repositories] 6 | boost = ./external/boost 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | buck-out 2 | .buckd 3 | .tags 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "googletest"] 2 | path = external/gtest/googletest 3 | url = https://github.com/google/googletest.git 4 | -------------------------------------------------------------------------------- /BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "sharp", 3 | exported_deps = [ 4 | "//Channel:Channel", 5 | "//Defer:Defer", 6 | "//Executor:Executor", 7 | "//ForEach:ForEach", 8 | "//Functional:Functional", 9 | "//Future:Future", 10 | "//Concurrent:Concurrent", 11 | "//Overload:Overload", 12 | "//OrderedContainer:OrderedContainer", 13 | "//Overload:Overload", 14 | "//Portability:Portability", 15 | "//Range:Range", 16 | "//Singleton:Singleton", 17 | "//Tags:Tags", 18 | "//Threads:Threads", 19 | "//TransparentList:TransparentList", 20 | "//TypeSet:TypeSet", 21 | "//Utility:Utility", 22 | ], 23 | visibility = [ 24 | "PUBLIC", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /Channel/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Channel", 3 | header_namespace = "sharp/Channel", 4 | deps = [ 5 | "//Tags:Tags", 6 | "//Try:Try", 7 | "//Concurrent:Concurrent", 8 | "//ForEach:ForEach", 9 | "//Defer:Defer", 10 | "//Functional:Functional", 11 | "//Traits:Traits", 12 | "//Portability:Portability", 13 | ], 14 | exported_headers = [ 15 | "Channel.hpp", 16 | "Channel.ipp", 17 | ], 18 | visibility = [ 19 | "PUBLIC", 20 | ], 21 | 22 | tests = [ 23 | "//Channel/test:test", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /Channel/README.md: -------------------------------------------------------------------------------- 1 | `Channel` 2 | --------- 3 | 4 | An implementation of channels as found in the [Go programming 5 | langauge](https://tour.golang.org/concurrency/2) 6 | 7 | ## Design decisions 8 | 9 | - The interface of channels in Go has been mimicked as much as possible, while 10 | trying to retain the idiomatic C++ representation of each operation. For 11 | example the [`select()`](https://goo.gl/DVQtEh) statement has been designed 12 | to feel familiar to Go users, while utilizing C++ features to work as 13 | efficiently as possible 14 | 15 | - This library does not make any assumptions about the programming model of the 16 | user's code. For example if the goal is shared dynamic ownership then 17 | simply dump the channels into `std::shared_ptr` instances 18 | 19 | - C++ has RAII and exceptions. As a consequence these channels do not support 20 | the more primitive form of error passing through values, but rather have 21 | utilities to send exceptions, see the 22 | [`send_exception`](https://goo.gl/Dx8T4U) method 23 | 24 | - Unlike other implementations, this implementation does not have the buffer 25 | size set at compile time. This allows users to control the asynchronoicity 26 | of APIs and also allows them to engineer code which is independent of the 27 | buffer size into a compiled form. Such a design allows users to 28 | interoperate with libraries that use channels for synchronization with their 29 | own channel instances 30 | 31 | ## Example usage 32 | 33 | ```c++ 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | using std::cout; 42 | using std::endl; 43 | 44 | template 45 | void sum(InputIt begin, InputIt end, sharp::Channel& c) { 46 | auto sum = std::accumulate(begin, end, 0); 47 | c.send(sum); 48 | } 49 | 50 | int main() { 51 | 52 | auto s = std::vector{7, 2, 8, -9, 4, 0}; 53 | 54 | sharp::Channel c; 55 | std::thread{[&]() { sum(s.begin(), s.begin() + s.size()/2, c); }}.detach(); 56 | std::thread{[&]() { sum(s.begin() + s.size()/2, s.end(), c); }}.detach(); 57 | 58 | auto x = c.read(); 59 | auto y = c.read(); 60 | 61 | cout << x << y << x + y << endl; 62 | 63 | return 0; 64 | } 65 | ``` 66 | 67 | ### Buffered channels 68 | 69 | ```c++ 70 | #include 71 | 72 | #include 73 | 74 | using std::cout; 75 | using std::endl; 76 | 77 | int main() { 78 | sharp::Channel c{2}; 79 | ch.send(1); 80 | ch.send(2); 81 | cout << ch.read() << endl; 82 | cout << ch.read() << endl; 83 | 84 | return 0; 85 | } 86 | ``` 87 | 88 | 89 | ### Range based channeling 90 | 91 | ```c++ 92 | #include 93 | #include 94 | 95 | void fibonacci(sharp::Channel& c) { 96 | auto x = 0, y = 1; 97 | 98 | for (auto i = 0; i < 10; ++i) { 99 | 100 | auto to_send = x, new_y = x + y; 101 | x = y; 102 | y = new_y; 103 | 104 | c.send(to_send); 105 | } 106 | 107 | c.close(); 108 | } 109 | 110 | int main() { 111 | 112 | sharp::Channel c; 113 | std::thread{[&]() { fibonacci(c); }}.detach(); 114 | 115 | for (auto i : c) { 116 | cout < i << endl; 117 | } 118 | 119 | return 0; 120 | } 121 | ``` 122 | 123 | 124 | ### Compile time channel multiplexing via `select` 125 | The compile time `select` API implementation picks whether you are waiting on 126 | a read or a write based on the signature of the function passed in along with 127 | the channel. 128 | 129 | ```c++ 130 | #include 131 | 132 | #include 133 | 134 | using std::cout; 135 | 136 | void fibonacci(sharp::Channel& c, sharp::Channel& quit) { 137 | auto x = 0, y = 1; 138 | 139 | auto should_continue = true; 140 | while (should_continue) { 141 | 142 | sharp::select( 143 | sharp::make_case(c, [&]() -> int { 144 | auto to_send = x, new_y = x + y; 145 | x = y; 146 | y = new_y; 147 | 148 | return to_send; 149 | }), 150 | 151 | sharp::make_case(quit, [&](auto) { 152 | cout << "Quitting" << endl; 153 | should_continue = false; 154 | }) 155 | ); 156 | } 157 | } 158 | 159 | int main() { 160 | sharp::Channel c; 161 | sharp::Channel quit; 162 | 163 | std::thread{[&]() { 164 | 165 | for (auto i = 0; i < 10; ++i) { 166 | cout << c.read() << endl; 167 | } 168 | quit.send(0); 169 | }}.detach(); 170 | 171 | fibonacci(c, quit); 172 | } 173 | ``` 174 | 175 | -------------------------------------------------------------------------------- /Channel/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Channel:Channel", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Channel/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | constexpr auto number_iterations = 1e3; 12 | 13 | TEST(Channel, BasicTest) { 14 | sharp::Channel ch{1}; 15 | ch.send(1); 16 | EXPECT_EQ(ch.read(), 1); 17 | } 18 | 19 | TEST(Channel, TryReadFail) { 20 | sharp::Channel ch_one; 21 | EXPECT_FALSE(ch_one.try_read()); 22 | 23 | sharp::Channel ch_two{2}; 24 | EXPECT_FALSE(ch_two.try_read()); 25 | } 26 | 27 | TEST(Channel, SendTwoValues) { 28 | sharp::Channel c{2}; 29 | c.send(2); 30 | c.send(3); 31 | EXPECT_EQ(c.read(), 2); 32 | EXPECT_EQ(c.read(), 3); 33 | EXPECT_FALSE(c.try_read()); 34 | auto th = std::thread{[&]() { 35 | EXPECT_EQ(c.read(), 4); 36 | }}; 37 | c.send(4); 38 | th.join(); 39 | } 40 | 41 | TEST(Channel, SendTwoValuesModified) { 42 | sharp::Channel c{2}; 43 | c.send(2); 44 | c.send(3); 45 | EXPECT_EQ(c.read(), 2); 46 | c.send(4); 47 | EXPECT_EQ(c.read(), 3); 48 | c.send(5); 49 | EXPECT_EQ(c.read(), 4); 50 | EXPECT_EQ(c.read(), 5); 51 | EXPECT_FALSE(c.try_read()); 52 | auto th = std::thread{[&]() { 53 | EXPECT_EQ(c.read(), 4); 54 | }}; 55 | c.send(4); 56 | th.join(); 57 | } 58 | 59 | TEST(Channel, UnbufferedThreadedSend) { 60 | sharp::Channel c; 61 | 62 | // the results vector, although this is not locked with a mutex, access to 63 | // this is atomic since the read thread only reads a value after it has 64 | // been written to 65 | auto results = std::vector{}; 66 | results.resize(number_iterations); 67 | 68 | // the random number generator 69 | auto rng = std::mt19937{}; 70 | rng.seed(std::random_device()()); 71 | auto dist = std::uniform_int_distribution{}; 72 | 73 | auto th_one = std::thread{[&]() { 74 | for (auto i = 0; i < number_iterations; ++i) { 75 | auto current_value = dist(rng); 76 | results[i] = current_value; 77 | c.send(current_value); 78 | } 79 | }}; 80 | 81 | auto th_two = std::thread{[&]() { 82 | for (auto i = 0; i < number_iterations; ++i) { 83 | auto val = c.read(); 84 | EXPECT_EQ(val, results[i]); 85 | } 86 | }}; 87 | 88 | th_one.join(); 89 | th_two.join(); 90 | } 91 | 92 | // TEST(Channel, SelectBasicRead) { 93 | // sharp::Channel c{1}; 94 | // c.send(1); 95 | // int val = 0; 96 | // sharp::select( 97 | // std::make_pair(std::ref(c), [&val](auto value) { 98 | // ++val; 99 | // EXPECT_EQ(value, 1); 100 | // }), 101 | // std::make_pair(std::ref(c), []() -> int { 102 | // EXPECT_TRUE(false); 103 | // return 0; 104 | // }) 105 | // ); 106 | // EXPECT_EQ(val, 1); 107 | // } 108 | 109 | // TEST(Channel, SelectBasicWrite) { 110 | // sharp::Channel c{1}; 111 | // int val = 0; 112 | // sharp::select( 113 | // std::make_pair(std::ref(c), [](auto) { 114 | // EXPECT_TRUE(false); 115 | // }), 116 | // std::make_pair(std::ref(c), [&val]() -> int { 117 | // ++val; 118 | // return 2; 119 | // }) 120 | // ); 121 | // auto value = c.try_read(); 122 | // EXPECT_TRUE(value); 123 | // EXPECT_EQ(value.value(), 2); 124 | // } 125 | 126 | template 127 | void sum(InputIt begin, InputIt end, sharp::Channel& c) { 128 | auto sum = std::accumulate(begin, end, 0); 129 | c.send(sum); 130 | } 131 | 132 | TEST(Channel, ExampleOneTest) { 133 | 134 | auto s = std::vector{7, 2, 8, -9, 4, 0}; 135 | 136 | sharp::Channel c; 137 | std::thread{[&]() { sum(s.begin(), s.begin() + s.size()/2, c); }}.detach(); 138 | std::thread{[&]() { sum(s.begin() + s.size()/2, s.end(), c); }}.detach(); 139 | 140 | auto x = c.read(); 141 | auto y = c.read(); 142 | 143 | EXPECT_TRUE(x == 17 || x == -5); 144 | EXPECT_TRUE(y == 17 || y == -5); 145 | } 146 | 147 | 148 | // void fibonacci(sharp::Channel& c, sharp::Channel& quit) { 149 | // auto x = 0, y = 1; 150 | 151 | // auto should_continue = true; 152 | // while (should_continue) { 153 | 154 | // sharp::select( 155 | // std::make_pair(std::ref(c), [&] () -> int { 156 | 157 | // auto to_send = x, new_y = x + y; 158 | // x = y; 159 | // y = new_y; 160 | 161 | // return to_send; 162 | // }), 163 | 164 | // std::make_pair(std::ref(quit), [&](auto) { 165 | // should_continue = false; 166 | // }) 167 | // ); 168 | // } 169 | // } 170 | 171 | // TEST(Channel, ExampleTwoTest) { 172 | 173 | // for (auto i = 0; i < number_iterations; ++i) { 174 | // sharp::Channel c; 175 | // sharp::Channel quit; 176 | // auto results = std::vector{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}; 177 | 178 | // auto th = std::thread{[&]() { 179 | // for (auto i = 0; i < 10; ++i) { 180 | // auto val = c.read(); 181 | // EXPECT_EQ(val, results[i]); 182 | // } 183 | // quit.send(0); 184 | // }}; 185 | 186 | // fibonacci(c, quit); 187 | // th.join(); 188 | // } 189 | // } 190 | 191 | // void fibonacci_range(sharp::Channel& c) { 192 | // auto x = 0, y = 1; 193 | 194 | // for (auto i = 0; i < 10; ++i) { 195 | 196 | // auto to_send = x, new_y = x + y; 197 | // x = y; 198 | // y = new_y; 199 | 200 | // c.send(to_send); 201 | // } 202 | 203 | // c.close(); 204 | // } 205 | 206 | // TEST(Channel, RangeTest) { 207 | // sharp::Channel c; 208 | // std::thread{[&]() { 209 | // fibonacci_range(c); 210 | // }}.detach(); 211 | // auto results = std::vector{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}; 212 | 213 | // auto counter = 0; 214 | // for (auto val : c) { 215 | // EXPECT_EQ(val, results[counter++]); 216 | // } 217 | // } 218 | -------------------------------------------------------------------------------- /Concurrent/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Concurrent", 3 | header_namespace = "sharp/Concurrent", 4 | deps = [ 5 | "//Defer:Defer", 6 | "//ForEach:ForEach", 7 | "//Functional:Functional", 8 | "//Tags:Tags", 9 | "//Traits:Traits", 10 | "//Threads:Threads", 11 | "//TransparentList:TransparentList", 12 | "//Portability:Portability", 13 | ], 14 | exported_headers = [ 15 | "Concurrent.pre.hpp", 16 | "Concurrent.hpp", 17 | "Concurrent.ipp", 18 | ], 19 | visibility = [ 20 | "PUBLIC", 21 | ], 22 | 23 | tests = [ 24 | "//Concurrent/test:test", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /Concurrent/README.md: -------------------------------------------------------------------------------- 1 | `Concurrent` Yet another concurrency abstraction 2 | ------------ 3 | 4 | `Concurrent` (originally named `ThreadSafeData`) contains an abstraction that 5 | I implemented in my OS class that I find useful in concurrent programming. 6 | This library tries to help you write concurrent code without some of the 7 | drawbacks of free mutexes and condition variables. 8 | 9 | Mutexes are logically associated with the data they protect, condition 10 | variables are associated with the threads they block given a certain 11 | condition. This class tries to package both of those into one simple bite 12 | sized concurrency primitive 13 | 14 | This is what you would do if you wanted to have three data items that could 15 | potentially be accessed concurrently from different threads 16 | 17 | ```c++ 18 | std::mutex mtx_one; 19 | std::condition_variable cv_one; 20 | std::vector critical_vector; 21 | std::mutex mtx_two 22 | std::condition_variable cv_two; 23 | std::deque critical_deque; 24 | std::mutex mtx_three; 25 | std::condition_variable cv_three; 26 | std::unordered_map information_map; 27 | ``` 28 | 29 | This is ugly, and it is not clear which mutex is meant to protect which data 30 | item. Throw in some more condition variables for different conditions in 31 | there and the code becomes chaotic, hard to read, hard to reason about and 32 | hard to maintain 33 | 34 | Instead you could have something like the following 35 | 36 | ```c++ 37 | sharp::Concurrent> vector; 38 | sharp::Concurrent> deque; 39 | sharp::Concurrent> map; 40 | ``` 41 | 42 | This makes the semantics of the code clear and self documenting - There are 43 | three data items that will possibly be accessed from different threads 44 | concurrently 45 | 46 | The API of this class makes it very hard for you to access a concurrently 47 | accessed data item without the proper locking. This avoids reliance on 48 | [compiler annotations](https://goo.gl/UW5eyi), linters, etc. 49 | 50 | ### The interface 51 | 52 | The simplest use case of this library is to execute code that will be 53 | synchronized off an internal mutex associated with the data item. This can be 54 | done nicely with lambdas 55 | 56 | ```c++ 57 | auto vec = sharp::Concurrent>{}; 58 | auto size = vec.synchronized([](auto& vec) { 59 | vec.push_back(1); 60 | return vec.size(); 61 | }); 62 | ``` 63 | 64 | This executes the size read on the vector synchronously off a mutex associated 65 | with the vector internally. The library manages that for you 66 | 67 | The library also provides a simple RAII managed locking mechanism that avoids 68 | having to rely on other external RAII wrappers like `std::unique_lock` and 69 | `std::lock_guard` for just locking and unlocking a mutex. The interface 70 | handles that for you 71 | 72 | ```c++ 73 | auto lock = vec.lock(); 74 | lock->push_back(1); 75 | for (auto integer : *lock) { 76 | cout << integer << endl; 77 | } 78 | lock.unlock(); 79 | ``` 80 | 81 | This avoids the problem of having to construct RAII wrappers around mutex 82 | classes for better more exception safe code. There is no way to lock the 83 | mutex without being robust in the face of exceptions 84 | 85 | Further the library optimizes contention when the program is written in a 86 | const correct manner. When the concurrent object is const, a `lock()` call 87 | tries to acquire a shared lock instead of an exclusive unique lock, this helps 88 | increase throughput in high read scenarios for reader threads 89 | 90 | ```c++ 91 | // this acquires a shared lock on the internal mutex if possible 92 | auto lock = sharp::as_const(data).lock(); 93 | cout << lock->size() << endl; 94 | ``` 95 | 96 | Conditional critical sections (inspired by [Google's 97 | `absl::Mutex`](https://goo.gl/JhhGFp)) are also supported. The interface here 98 | tries to eliminate some of the drawbacks of condition variables, for example - 99 | manual signalling, broadcasting, coarse lock contention on wakeups, thundering 100 | herds (especially with reader writer locks), forgetting to signal, etc. 101 | 102 | ```c++ 103 | // thread 1 104 | auto lock = data.lock(); 105 | lock.wait([](auto& data) { 106 | return data.is_ready(); 107 | }); 108 | cout << data.get() << endl; 109 | 110 | // thread 2 111 | auto lock = data.lock(); 112 | lock->set_value(4); 113 | lock.unlock(); 114 | ``` 115 | 116 | Here when thread 2 is done writing and data is ready, thread 1 will be woken 117 | up. Simple. No signalling. No broadcasting. No bugs 118 | -------------------------------------------------------------------------------- /Concurrent/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Concurrent:Concurrent", 8 | "//Utility:Utility", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /Defer/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Defer", 3 | header_namespace = "sharp/Defer", 4 | exported_headers = [ 5 | "Defer.hpp", 6 | "Defer.ipp", 7 | ], 8 | visibility = [ 9 | "PUBLIC", 10 | ], 11 | 12 | tests = [ 13 | "//Defer/test:test", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /Defer/Defer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Defer.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * This file implements the DEFER macro, this allows users to create functions 6 | * that will execute upon function exit, similar to the native Go defer() 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | namespace sharp { 15 | 16 | /** 17 | * @function defer 18 | * 19 | * Returns the deferred state that should be stored somewhere in a local 20 | * variable, this is the idiomatic way to defer in C++, with a variable that 21 | * stores the function and then executes on destruction 22 | */ 23 | template 24 | auto defer(Func&& func_in); 25 | 26 | /** 27 | * @function defer_guard 28 | * 29 | * Analagous to the difference between unique_lock and lock_guard; this 30 | * function is an optimization over the last one in the case where you don't 31 | * want the ability to reset a deferred function to a no-op 32 | */ 33 | template 34 | auto defer_guard(Func&& func_in); 35 | 36 | } // namespace sharp 37 | 38 | #include 39 | -------------------------------------------------------------------------------- /Defer/Defer.ipp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace sharp { 9 | 10 | namespace detail { 11 | 12 | /** 13 | * @class Defer 14 | * 15 | * The basic implementation of a releasable defer 16 | */ 17 | template 18 | class Defer { 19 | public: 20 | 21 | /** 22 | * constructors for the class just initialize the member variable that 23 | * stores the function with the passed in function 24 | */ 25 | Defer(const Func& func_in) noexcept( 26 | std::is_nothrow_copy_constructible::value) 27 | : should_execute{true}, func{func_in} {} 28 | Defer(Func&& func_in) noexcept( 29 | std::is_nothrow_move_constructible::value) 30 | : should_execute{true}, func{std::move(func_in)} {} 31 | 32 | /** 33 | * Destructor just executes the function on return and nothing else 34 | */ 35 | ~Defer() { 36 | if (should_execute) { 37 | func(); 38 | } 39 | } 40 | 41 | /** 42 | * @function reset 43 | * 44 | * This method resets the internal state of the deferred function so 45 | * that it will no longer be executed on destruction or when the scope 46 | * finishes 47 | */ 48 | void reset() const noexcept { 49 | this->should_execute = false; 50 | } 51 | 52 | private: 53 | 54 | /** 55 | * Whether or not the function should execute the function stored within 56 | * it on return 57 | */ 58 | bool should_execute; 59 | 60 | /** 61 | * The function instance that will be called unless the deferred 62 | * function is reset 63 | */ 64 | Func func; 65 | }; 66 | 67 | template 68 | class DeferGuard { 69 | public: 70 | 71 | /** 72 | * constructors for the class just initialize the member variable that 73 | * stores the function with the passed in function 74 | */ 75 | DeferGuard(const Func& func_in) noexcept( 76 | std::is_nothrow_copy_constructible::value) 77 | : func{func_in} {} 78 | DeferGuard(Func&& func_in) noexcept( 79 | std::is_nothrow_move_constructible::value) 80 | : func{std::move(func_in)} {} 81 | 82 | /** 83 | * Destructor just executes the function on return and nothing else 84 | */ 85 | ~DeferGuard() { 86 | func(); 87 | } 88 | 89 | private: 90 | 91 | /** 92 | * The function instance that will be called unless the deferred 93 | * function is reset 94 | */ 95 | Func func; 96 | }; 97 | 98 | } // namespace detail 99 | 100 | template 101 | auto defer(Func&& func_in) { 102 | return detail::Defer>{std::forward(func_in)}; 103 | } 104 | 105 | template 106 | auto defer_guard(Func&& func_in) { 107 | return detail::DeferGuard>{std::forward(func_in)}; 108 | } 109 | 110 | } // namespace sharp 111 | -------------------------------------------------------------------------------- /Defer/README.md: -------------------------------------------------------------------------------- 1 | `Defer` 2 | ------- 3 | 4 | A module implementing `defer` similar to the in-built language construct in Go 5 | (https://blog.golang.org/defer-panic-and-recover), with the same semantics 6 | similar usage. There is no reason to include a panic and a recover 7 | implementation because C++ offers a different, and more efficient alternative 8 | (try and catch blocks) 9 | 10 | The following is an example of how you would go about using defer to force 11 | close a database connection in an RAII manner 12 | 13 | ```C++ 14 | void something() { 15 | auto database_connection_ptr = connect_db(); 16 | 17 | // mark the database to be closed before exit 18 | auto deferred = defer([&]() { 19 | database_connection_ptr->close(); 20 | }); 21 | 22 | // use database 23 | } 24 | ``` 25 | 26 | Similarly defer calls can be chained and each deferable function call is 27 | pushed onto a stack and when stack is collapsed the functions are ran in the 28 | reverse order of registration 29 | 30 | ```C++ 31 | void something() { 32 | auto database_connection_ptr = connect_db(); 33 | 34 | // close the database connection before this scope finishes 35 | auto deferred_one = defer([&]() { 36 | database_connection_ptr->close(); 37 | }); 38 | 39 | // wait for any threads that the database has running to finish before 40 | // exiting 41 | // 42 | // the difference between defer_guard and defer is analagous to the 43 | // difference between unique_lock and lock_guard, defer_guard does not 44 | // branch when executing the destructor, in case the user has reset() the 45 | // deferred state 46 | auto deferred_two = defer_guard([&]() { 47 | if (database_connection_ptr->has_running_threads()) { 48 | database_connection_ptr->join_threads(); 49 | } 50 | }); 51 | 52 | // use database 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /Defer/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Defer:Defer", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Defer/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST(Defer, basic_test) { 6 | auto is_set = false; 7 | { 8 | auto deferred = sharp::defer([&is_set]() { 9 | is_set = true; 10 | }); 11 | } 12 | EXPECT_TRUE(is_set); 13 | } 14 | -------------------------------------------------------------------------------- /Enumerate/Emnumerate.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using std::cout; 8 | using std::endl; 9 | 10 | template 11 | struct WhichType; 12 | 13 | 14 | template 15 | class enumerate 16 | { 17 | private: 18 | size_t index = 0; 19 | 20 | decltype(std::declval().begin()) 21 | decltype(container_copy.begin()) start_it; 22 | decltype(start_it) end_it; 23 | 24 | 25 | 26 | decltype(start_it) get_start() 27 | { 28 | return start_it; 29 | } 30 | 31 | decltype(end_it) get_end() 32 | { 33 | return end_it; 34 | } 35 | 36 | 37 | 38 | public: 39 | template 40 | enumerate(TT&& container) 41 | { 42 | start_it = container.begin(); 43 | end_it = container.end(); 44 | 45 | } 46 | 47 | 48 | class ret_obj 49 | { 50 | //friend class enumerate; 51 | public: 52 | decltype(start_it) s; 53 | decltype(start_it) e; 54 | size_t index = 0; 55 | 56 | ret_obj(decltype(start_it) it) 57 | { 58 | e = it; 59 | } 60 | 61 | ret_obj(enumerate& it) 62 | { 63 | s = it.get_start(); 64 | e = it.get_end(); 65 | } 66 | 67 | bool operator!=(const ret_obj& other) 68 | { 69 | return this->s != other.e; 70 | } 71 | 72 | ret_obj& operator++() 73 | { 74 | this->index +=1; 75 | ++s; 76 | return *this; 77 | } 78 | 79 | auto operator*() { 80 | return std::make_pair(std::ref(*this->s), this->index); 81 | } 82 | 83 | };//ret _obj class 84 | 85 | ret_obj begin() 86 | { 87 | return ret_obj(*this); 88 | } 89 | 90 | ret_obj end() 91 | { 92 | ret_obj a(end_it); 93 | return a; 94 | } 95 | 96 | }; 97 | 98 | template 99 | auto enumerate_func(T& container) 100 | { 101 | enumerate en(container); 102 | return en; 103 | } 104 | 105 | int main() 106 | { 107 | std::vector vec{99,2,83,99,3}; 108 | 109 | for (auto x : enumerate_func(vec)) { 110 | cout << x.first << " " << x.second << endl; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /Enumerate/Readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aary/sharp/e96b0c69cb4c6296b1f56585a958cd85a7a90ab6/Enumerate/Readme.md -------------------------------------------------------------------------------- /Executor/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Executor", 3 | deps = [ 4 | "//Functional:Functional", 5 | ], 6 | header_namespace = "sharp/Executor", 7 | exported_headers = [ 8 | "Executor.hpp", 9 | "InlineExecutor.hpp", 10 | ], 11 | srcs = [ 12 | "Executor.cpp", 13 | ], 14 | visibility = [ 15 | "PUBLIC", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /Executor/Executor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace sharp { 4 | 5 | std::size_t Executor::num_pending_closures() const { 6 | return 0; 7 | } 8 | 9 | } // namespace sharp 10 | -------------------------------------------------------------------------------- /Executor/Executor.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Executor.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * An executor is an interface that can be used by clients to pass off units 6 | * of work, that will be executed as determined by the implementation of the 7 | * executor. It is a basic building block for event driven systems that need 8 | * a systematic way to execute blocks of code 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace sharp { 18 | 19 | /** 20 | * @class Executor 21 | * 22 | * This class encapsulates the execution of a passed in function object. The 23 | * function object is either executed inline on the current thread or on 24 | * another thread 25 | * 26 | * The interface to it is simple, when you want a function executed either now 27 | * or at some time in the immediate future (because there might be other 28 | * things executing currently) then you call the .add() method. The .add() 29 | * method will either execute the function immediately or pass it off to 30 | * another thread to execute later. The execution policy (either to execute 31 | * inline or in another thread) will be determined by the implementation of 32 | * the executor 33 | * 34 | * All implementations of executor classes will derive from this one base 35 | * class and then specialize the .add() member function 36 | */ 37 | class Executor { 38 | public: 39 | 40 | /** 41 | * Virtual destructor for the Executor class 42 | */ 43 | virtual ~Executor() {} 44 | 45 | /** 46 | * The add() function which consists of most of the logic and usage of 47 | * this Executor class, this function will be used to add function objects 48 | * to this executor, either to be executed now or later in the future 49 | * 50 | * The exact execution of the closure will be dependent on the 51 | * implementation of the executor, for example it can either be executed 52 | * inline or on another thread 53 | */ 54 | virtual void add(sharp::Function closure) = 0; 55 | 56 | /** 57 | * Returns the number of function objects waiting to be executed 58 | * 59 | * This is intended for debugging/logging and other issues of the sort. 60 | * Any other uses are inherently prone to race conditions because other 61 | * threads may still be executing the closures 62 | */ 63 | virtual std::size_t num_pending_closures() const; 64 | }; 65 | 66 | } // namespace sharp 67 | 68 | #include 69 | -------------------------------------------------------------------------------- /Executor/InlineExecutor.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file InlineExecutor.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * The simplest possible implementation of the Executor interface, all 6 | * callbacks are executed immediately on calling the .add() function on the 7 | * current thread 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace sharp { 18 | 19 | class InlineExecutor : public Executor { 20 | public: 21 | 22 | /** 23 | * Destructor does nothing as there is no state to maintain here, all 24 | * functions are executed immediately 25 | */ 26 | ~InlineExecutor() override {} 27 | 28 | /** 29 | * Do the thing 30 | */ 31 | void add(sharp::Function closure) override { 32 | closure(); 33 | } 34 | 35 | /** 36 | * At all times there are no pending closures in this executor, as all of 37 | * them are executed immediately 38 | */ 39 | std::size_t num_pending_closures() const override { 40 | return 0; 41 | } 42 | 43 | /** 44 | * Returns the thread singleton inline executor instance 45 | */ 46 | static Executor* get() { 47 | static InlineExecutor executor; 48 | return &executor; 49 | } 50 | 51 | }; 52 | 53 | } // namespace sharp 54 | -------------------------------------------------------------------------------- /ForEach/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "ForEach", 3 | header_namespace = "sharp/ForEach", 4 | deps = [ 5 | "//Traits:Traits", 6 | ], 7 | headers = [ 8 | "ForEach.hpp", 9 | "ForEach.ipp", 10 | ], 11 | exported_headers = [ 12 | "ForEach.hpp", 13 | "ForEach.ipp", 14 | ], 15 | visibility = [ 16 | "PUBLIC", 17 | ], 18 | 19 | tests = [ 20 | "//ForEach/test:test", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /ForEach/ForEach.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Foreach.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * This file contains a generalized iteration algorithm that gets more 6 | * complicated as you use more features of the iteration 7 | */ 8 | 9 | #pragma once 10 | 11 | namespace sharp { 12 | 13 | /** 14 | * @function for_each 15 | * 16 | * This was imported to Facebook's C++ libraries here 17 | * https://github.com/facebook/folly/blob/master/folly/Foreach.h 18 | * 19 | * Iterate through values in a range, this works for ranges that have 20 | * std::get<> and std::tuple_size<> defined as well, ranges that have the 21 | * std::begin() and std::end() functions defined for them 22 | * 23 | * Polymorphic unary or binary lambdas can be passed in as arguments to this 24 | * function, the first type always has to be templated so as to accept 25 | * arguments of different types, the second argument is optional, but also 26 | * must be templated because the index at which the iteration currently is at 27 | * is also passed in for convenience, and this is represented as a 28 | * std::integral_constant type where x is the iteration number, for 29 | * example 30 | * 31 | * auto t = std::make_tuple(1, "string"); 32 | * sharp::for_each(t, [](auto thing, auto index) { 33 | * cout << thing << " at index " << index << endl; 34 | * }); 35 | * 36 | * The output for the above program would be 37 | * 38 | * 1 0 39 | * string 1 40 | * 41 | * and the static_cast to int is a constexpr expression so that will be 42 | * can be used in constexpr situations 43 | */ 44 | template 45 | constexpr Func for_each(TupleType&& tup, Func func); 46 | 47 | /** 48 | * This can be used by the user to control whether the loop breaks or not, 49 | * instances of loop_break or loop_continue should be returned by the function 50 | * passed to the for_each algorithm to determine breaking or continuing 51 | * 52 | * sharp::for_each(range, [](auto ele, auto index) { 53 | * if (index >= 3) { 54 | * return loop_break; 55 | * } 56 | * cout << ele << endl; 57 | * return loop_continue; 58 | * }); 59 | * 60 | * This works almost identically to break and continue in regular C++ loops, 61 | * except that if either continue or break is returned then the function 62 | * object must return an instance of either in each path 63 | */ 64 | namespace for_each_detail { 65 | enum class LoopControl : bool { 66 | BREAK = true, 67 | CONTINUE = false 68 | }; 69 | } // namespace for_each_detail 70 | 71 | constexpr auto loop_break = for_each_detail::LoopControl::BREAK; 72 | constexpr auto loop_continue = for_each_detail::LoopControl::CONTINUE; 73 | 74 | /** 75 | * Utility function that can be used to get elements at a given index in a 76 | * range, be it compile time or runtime 77 | * 78 | * This only works if the range offers random access iterators. Not a worry 79 | * though with compile time iterators 80 | * 81 | * The formal requirement for this to work is, if the range contains an 82 | * overloaded get<>() function (ADL defined free function or as a member 83 | * fucntion), then that will be used. If that does not exist then the begin 84 | * iterator will be examined to see if that is a random access iterator (as 85 | * deemed by the presence of 86 | * std::iterator_traits::iterator_category and that being the 87 | * same as std::random_access_iterator_tag, if that is so then the index will 88 | * be used to index from the starting position of the range and a reference 89 | * (whatever that may be qualified as based on the characteristics of the 90 | * iterator class, i.e. it can either be an xvalue or an lvalue) will be 91 | * returned to it 92 | */ 93 | template 94 | constexpr decltype(auto) fetch(Range&& range, Index&& index); 95 | 96 | } // namespace sharp 97 | 98 | #include 99 | -------------------------------------------------------------------------------- /ForEach/README.md: -------------------------------------------------------------------------------- 1 | `for_each` Iterate through anything 2 | ---------- 3 | 4 | This library provides an API for looping and iterating through any type of 5 | range at all. This does not have to be a runtime range like a `std::vector` 6 | but can also be a "range" that is defined at compile time like a `std::tuple` 7 | 8 | The library has high differential complexity as you use more features but the 9 | inherent simple use is really simple. Even simpler than the existing 10 | `std::for_each` algorithm 11 | 12 | ```c++ 13 | // With the standard std::for_each 14 | std::for_each(range.begin(), range.end(), [](auto element) { 15 | cout << element << endl; 16 | }); 17 | 18 | // With sharp::for_each 19 | sharp::for_each(range, [](auto element) { 20 | cout << element << endl; 21 | }); 22 | ``` 23 | 24 | Iterate with indices 25 | 26 | ```c++ 27 | auto range = std::make_tuple(1, 2); 28 | sharp::for_each(range, [](auto element, auto index) { 29 | cout << index << " : " << element << endl; 30 | }); 31 | ``` 32 | 33 | You can also break from a loop preemptively 34 | 35 | ```c++ 36 | auto range = std::make_tuple(1, 2); 37 | sharp::for_each(range, [](auto element, auto index) { 38 | if (index >= 2) { 39 | return sharp::loop_break;; 40 | } 41 | 42 | cout << index << " : " << element << endl; 43 | return sharp::loop_continue; 44 | }); 45 | ``` 46 | 47 | Iterate through a list and peek forwards and backwards with iterators 48 | 49 | ```c++ 50 | auto range = std::list{1, 2, 3}; 51 | sharp::for_each(range, [&](auto element, auto index, auto iterator) { 52 | if (should_remove(element, index)) { 53 | range.erase(iterator); 54 | } 55 | }); 56 | ``` 57 | 58 | ### `sharp::fetch` 59 | 60 | A convenience wrapper is provided to help make uniform the syntax for indexing 61 | and accessing elements within a range. 62 | 63 | ```c++ 64 | auto one = std::make_tuple(1, 2, 3); 65 | 66 | auto two = std::tuple{}; 67 | auto three = std::vector{0, 0, 0}; 68 | auto four = std::unordered_map{}; 69 | 70 | sharp::for_each(one, [](auto element, auto index) { 71 | sharp::fetch(two, index) = element * 2; 72 | sharp::fetch(three, index) = element * 2; 73 | sharp::fetch(four, index) = element * 2; 74 | }); 75 | ``` 76 | 77 | The loop code can remain the same no matter what. 78 | 79 | Take a look inside the headers `ForEach.hpp` and `ForEach.ipp` for more 80 | documentation on the exact workings of the algorithm and utility 81 | 82 | ### Avoiding common looping pitfalls 83 | 84 | ```c++ 85 | for (const auto element : make_optional(vec).value()) { 86 | cout << element << endl; 87 | } 88 | ``` 89 | 90 | Spot the bug? 91 | 92 | The expression on the right in the range based for loop is an xvalue, and an 93 | xvalue's lifetime is not guaranteed to be extended, and in this case it is not 94 | extended. As a result you will be looping over a container that has already 95 | been destroyed. 96 | 97 | If however you were to use `sharp::for_each`, this problem would be alleviated 98 | 99 | ```c++ 100 | sharp::for_each(make_optional(vec).value(), [](auto element) { 101 | cout << element << endl; 102 | }); 103 | ``` 104 | 105 | Here the lifetime of the object bound to the function parameter of 106 | `sharp::for_each` will be extended until the function has finished executing. 107 | Therefore all looping code that you write will execute on a safe range that 108 | will be destroyed after the loop 109 | 110 | ### Performance benchmarks 111 | 112 | Tuple-like loops are unrolled manually and cause an O(1) template 113 | instantiation overhead. So build times are not greatly affected 114 | 115 | Further performance benchmarks show that the library performs just as well as 116 | a native loop and in some cases can even be faster. 117 | -------------------------------------------------------------------------------- /ForEach/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//ForEach:ForEach", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Functional/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Functional", 3 | header_namespace = "sharp/Functional", 4 | exported_headers = [ 5 | "Functional.hpp", 6 | "detail/Function.hpp", 7 | ], 8 | visibility = [ 9 | "PUBLIC", 10 | ], 11 | 12 | tests = [ 13 | "//Functional/test:test", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /Functional/Functional.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Functional.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * A hook that includes other header files in this module, that's it 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | -------------------------------------------------------------------------------- /Functional/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Functional:Functional", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Functional/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST(Functional, BasicFunctional) { 8 | // move only lambda that would otherwise not be compatible with 9 | // std::function 10 | auto int_uptr = std::make_unique(2); 11 | auto f = sharp::Function{[int_uptr = std::move(int_uptr)]() { 12 | return (*int_uptr) * 2; 13 | }}; 14 | EXPECT_EQ(f(), 4); 15 | } 16 | 17 | // #include 18 | // #include 19 | // #include 20 | // #include 21 | 22 | // using namespace std; 23 | 24 | // struct profiler 25 | // { 26 | // std::string name; 27 | // std::chrono::high_resolution_clock::time_point p; 28 | // profiler(std::string const &n) : 29 | // name(n), p(std::chrono::high_resolution_clock::now()) { } 30 | // ~profiler() 31 | // { 32 | // using dura = std::chrono::duration; 33 | // auto d = std::chrono::high_resolution_clock::now() - p; 34 | // std::cout << name << ": " 35 | // << std::chrono::duration_cast(d).count() 36 | // << std::endl; 37 | // } 38 | // }; 39 | // #define PROFILE_BLOCK(pbn) profiler _pfinstance(pbn) 40 | 41 | // TEST(Functional, PerformanceTest) { 42 | 43 | // std::srand(std::time(0)); 44 | // int x{0}; 45 | // for(int xx = 0; xx < 5; ++xx) 46 | // { 47 | // { 48 | // PROFILE_BLOCK("std::function"); 49 | // for (auto i = 0; i < 1000; ++i) { 50 | // x = std::rand(); 51 | // std::function t2 = [&x](int i){ return i + x; }; 52 | // std::function t1 = [&x, &t2](int i){ x = t2(i); }; 53 | // for(int j = 0; j < 1000000; ++j) t1(j); 54 | // } 55 | // } 56 | 57 | // { 58 | // PROFILE_BLOCK("sharp::Function"); 59 | // for (auto i = 0; i < 1000; ++i) { 60 | // x = std::rand(); 61 | // sharp::Function t2 = [&x](int i){ return i + x; }; 62 | // sharp::Function t1 = [&x, &t2](int i){ x = t2(i); }; 63 | // for(int j = 0; j < 1000000; ++j) t1(j); 64 | // } 65 | // } 66 | // } 67 | // } 68 | 69 | -------------------------------------------------------------------------------- /Future/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Future", 3 | header_namespace = "sharp/Future", 4 | deps = [ 5 | "//Range:Range", 6 | "//Tags:Tags", 7 | "//Defer:Defer", 8 | "//Traits:Traits", 9 | "//Utility:Utility", 10 | "//ForEach:ForEach", 11 | "//Functional:Functional", 12 | "//Executor:Executor", 13 | ], 14 | exported_headers = [ 15 | "Future.hpp", 16 | "Future.ipp", 17 | "Promise.hpp", 18 | "Promise.ipp", 19 | "SharedFuture.hpp", 20 | "SharedFuture.ipp", 21 | "FutureError.hpp", 22 | "detail/FutureImpl.hpp", 23 | "detail/FutureImpl.ipp", 24 | "detail/Future-pre.hpp", 25 | ], 26 | srcs = [ 27 | "FutureError.cpp", 28 | ], 29 | visibility = [ 30 | "PUBLIC", 31 | ], 32 | tests = [ 33 | "//Future/test:test", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /Future/FutureError.cpp: -------------------------------------------------------------------------------- 1 | #include "FutureError.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace sharp { 8 | 9 | namespace detail { 10 | const std::string BROKEN_PROMISE{"broken promise"}; 11 | const std::string FUTURE_ALREADY_RETRIEVED{"future already retrieved"}; 12 | const std::string PROMISE_ALREADY_SATISFIED{"promise already satisfied"}; 13 | const std::string NO_STATE{"no state"}; 14 | } // namespace detail 15 | 16 | /** 17 | * @class FutureErrorCategory 18 | * 19 | * The class that is the representative error category for future related 20 | * errors 21 | */ 22 | class FutureErrorCategory : public std::error_category { 23 | public: 24 | FutureErrorCategory() : std::error_category{} {} 25 | const char* name() const noexcept override; 26 | std::string message(int value) const override; 27 | }; 28 | 29 | const char* FutureErrorCategory::name() const noexcept { 30 | return "Future::FutureErrorCategory"; 31 | } 32 | 33 | std::string FutureErrorCategory::message(int value) const { 34 | 35 | // assert that the integer passed to FutureErrorCategory is within the 36 | // range of the enumeration, otherwise there will be undefined behavior 37 | assert(value <= static_cast(FutureErrorCode::no_state)); 38 | switch (static_cast(value)) { 39 | case FutureErrorCode::broken_promise: 40 | return detail::BROKEN_PROMISE; 41 | break; 42 | case FutureErrorCode::future_already_retrieved: 43 | return detail::FUTURE_ALREADY_RETRIEVED; 44 | break; 45 | case FutureErrorCode::promise_already_satisfied: 46 | return detail::PROMISE_ALREADY_SATISFIED; 47 | break; 48 | case FutureErrorCode::no_state: 49 | return detail::NO_STATE; 50 | break; 51 | } 52 | } 53 | 54 | const std::error_category& future_error_category() { 55 | static const FutureErrorCategory FUTURE_ERROR_CATEGORY; 56 | return FUTURE_ERROR_CATEGORY; 57 | } 58 | 59 | } // namespace sharp 60 | -------------------------------------------------------------------------------- /Future/FutureError.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file FutureError.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * This file contains the error abstractions that are used by the Future 6 | * module 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace sharp { 16 | 17 | /** 18 | * @enum FutureErrorCode 19 | * 20 | * This enum contains conditions erroneous conditions that can be exhibited 21 | * when using the future and promise APIs 22 | * 23 | * broken_promise is the error that is thrown when a promise is destroyed 24 | * without being sent either a value or an exception 25 | * future_already_retrieved when get() is called on a future that has already 26 | * been retrieved 27 | * promise_already_satisfied when you try and store a value or exception in a 28 | * promise that already has one of those stored 29 | * no_state attempt to access future or promise methods when there is no 30 | * shared state 31 | */ 32 | enum class FutureErrorCode : int { 33 | broken_promise, 34 | future_already_retrieved, 35 | promise_already_satisfied, 36 | no_state 37 | }; 38 | 39 | /** 40 | * @function future_error_category 41 | * 42 | * This function returns a singleton error_category instance that can be used 43 | * as the representative error_category object for Futures 44 | */ 45 | const std::error_category& future_error_category(); 46 | 47 | } // namespace sharp 48 | 49 | namespace std { 50 | 51 | /** 52 | * Overload for is_error_code_enum for traits purposes 53 | */ 54 | template <> 55 | struct is_error_code_enum : std::true_type {}; 56 | 57 | /** 58 | * Overload make_error_code and make_error_condition as is needed for 59 | * every custom error class 60 | */ 61 | inline error_code make_error_code(sharp::FutureErrorCode err) noexcept { 62 | return std::error_code{static_cast(err), 63 | sharp::future_error_category()}; 64 | } 65 | inline error_condition make_error_condition(sharp::FutureErrorCode err) 66 | noexcept { 67 | return error_condition{static_cast(err), 68 | sharp::future_error_category()}; 69 | } 70 | } // namespace std 71 | 72 | namespace sharp { 73 | 74 | class FutureError : std::logic_error { 75 | public: 76 | explicit FutureError(FutureErrorCode err) 77 | : std::logic_error{"sharp::FutureError: " 78 | + std::make_error_code(err).message()}, 79 | code_object{std::make_error_code(err)} {} 80 | const std::error_code& code() const { 81 | return this->code_object; 82 | } 83 | const char* what() const noexcept override { 84 | return "Future error"; 85 | } 86 | 87 | private: 88 | std::error_code code_object; 89 | }; 90 | 91 | } // namespace sharp 92 | -------------------------------------------------------------------------------- /Future/Promise.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Promise.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * This file contains an implementation of the Promise module, it is the 6 | * counterpart to futures. Futures represent a result of a computation that 7 | * is not yet finished. Internally futures are marked "finished" when their 8 | * value is set via a promise. Promises are like factory objects that are 9 | * used to both produce futures and fulfill them 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace sharp { 21 | 22 | /** 23 | * A forward declaration for the class that represents the shared state of the 24 | * future. The definition for this class will be shared by code in Future.cpp 25 | * as well as in Promise.cpp 26 | * 27 | * It contains almost all of the synchronization logic required. 28 | */ 29 | class FutureImpl; 30 | 31 | template 32 | class Promise { 33 | public: 34 | 35 | /** 36 | * Default constructor for a promise, constructs the promise with an empty 37 | * shared state 38 | */ 39 | Promise(); 40 | 41 | /** 42 | * Move constructor that move constructs a promise from another promise 43 | * object, this moves the shared state from the other promise into this 44 | * promise. 45 | * 46 | * After move construction the other promise has no shared state 47 | * 48 | * Promise objects are not copy constructible so delete that 49 | */ 50 | Promise(Promise&& other) = default; 51 | Promise(const Promise&) = delete; 52 | 53 | /** 54 | * If the shared state is ready, then the promise decrements the reference 55 | * count for the shared state. 56 | * 57 | * If it is not ready, then the promise stores an exception object of type 58 | * std::future_error with an error condition 59 | * std::future_errc::broken_promise, makes the shared state ready and 60 | * releases it 61 | */ 62 | ~Promise(); 63 | 64 | /** 65 | * Move assignment operator. This first abandons the current shared state 66 | * in the promise object and then swaps the shared state with the current 67 | * state 68 | * 69 | * Promise objects are not copy assignable. 70 | */ 71 | Promise& operator=(Promise&& other) noexcept; 72 | Promise& operator=(const Promise&) = delete; 73 | 74 | /** 75 | * A method that gets a future linked with the shared state, this future 76 | * can then be queried to get the object from the promise 77 | * 78 | * Throws an exception if the future has no shared state, in which case 79 | * the error category is set to no_state. The other condition where this 80 | * function throws an exception is where a future has already been 81 | * retrieved, the error category for the exception is set to 82 | * future_already_retrieved 83 | */ 84 | sharp::Future get_future(); 85 | 86 | /** 87 | * Atomically stores the value in the shared state and makes the state 88 | * ready. 89 | * 90 | * An exception is thrown if there is no shared state or if there is 91 | * already a value set in the future/promise pair 92 | * 93 | * The set_value function that accepts an emplace_construct::tag_t accepts 94 | * a list of arguments that are forwarded to the constructor of the type 95 | * in the shared state. This would mean that the object is constructed in 96 | * place 97 | */ 98 | void set_value(const Type& value); 99 | void set_value(Type&& value); 100 | template 101 | void set_value(sharp::emplace_construct::tag_t, Args&&... args); 102 | template 103 | void set_value(sharp::emplace_construct::tag_t, std::initializer_list il, 104 | Args&&... args); 105 | 106 | /** 107 | * Atomically stores an exception in the future associated with this 108 | * promise and makes the shared state ready 109 | * 110 | * An exeption is thrown if there is no shared state or if there is 111 | * already a value or exception in the shared state. 112 | */ 113 | void set_exception(std::exception_ptr ptr); 114 | 115 | private: 116 | 117 | /** 118 | * The shared state for the promise 119 | */ 120 | std::shared_ptr> shared_state; 121 | 122 | /** 123 | * Checks if the shared state exists, and if it doesn't then this function 124 | * throws the appropriate exception 125 | */ 126 | void check_shared_state() const; 127 | }; 128 | 129 | } // namespace sharp 130 | 131 | #include 132 | -------------------------------------------------------------------------------- /Future/Promise.ipp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace sharp { 11 | 12 | template 13 | Promise::Promise() 14 | : shared_state{std::make_shared>()} {} 15 | 16 | template 17 | Promise::~Promise() { 18 | if (this->shared_state) { 19 | if (!shared_state->is_ready()) { 20 | auto exc = FutureError{FutureErrorCode::broken_promise}; 21 | shared_state->set_exception(std::make_exception_ptr(exc)); 22 | } 23 | } 24 | } 25 | 26 | template 27 | Future Promise::get_future() { 28 | return Future{this->shared_state}; 29 | } 30 | 31 | template 32 | void Promise::set_value(const Type& value) { 33 | this->check_shared_state(); 34 | this->shared_state->set_value(value); 35 | } 36 | 37 | template 38 | void Promise::set_value(Type&& value) { 39 | this->check_shared_state(); 40 | this->shared_state->set_value(std::move(value)); 41 | } 42 | 43 | template 44 | template 45 | void Promise::set_value(sharp::emplace_construct::tag_t, 46 | Args&&... args) { 47 | this->check_shared_state(); 48 | this->shared_state->set_value(std::forward(args)...); 49 | } 50 | 51 | template 52 | template 53 | void Promise::set_value(sharp::emplace_construct::tag_t, 54 | std::initializer_list il, Args&&... args) { 55 | this->check_shared_state(); 56 | this->shared_state->set_value(il, std::forward(args)...); 57 | } 58 | 59 | template 60 | void Promise::set_exception(std::exception_ptr ptr) { 61 | this->check_shared_state(); 62 | this->shared_state->set_exception(ptr); 63 | } 64 | 65 | template 66 | void Promise::check_shared_state() const { 67 | if (!this->shared_state) { 68 | throw FutureError{FutureErrorCode::no_state}; 69 | } 70 | } 71 | 72 | } // namespace sharp 73 | -------------------------------------------------------------------------------- /Future/README.md: -------------------------------------------------------------------------------- 1 | `Future` An easy to use implementation of futures 2 | -------- 3 | 4 | -------------------------------------------------------------------------------- /Future/SharedFuture.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SharedFuture.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * A shared future is the same conceptually as a future, but instead of a 6 | * get() operation moving the value out of the future, a shared future's get() 7 | * operation copies the value out of the shared state of the future to the 8 | * caller 9 | * 10 | * As a result this has slightly different implications on the classes that 11 | * can be used to instantiate the future, unlike in a future 12 | * 13 | * Any missing documentation is the same as the corresponding documentation 14 | * for future 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | namespace sharp { 25 | 26 | template 27 | class SharedFuture : public detail::ComposableFuture>, 28 | public detail::ExecutableFuture> { 29 | public: 30 | 31 | /** 32 | * Traits that I thought might be useful 33 | */ 34 | using value_type = Type; 35 | 36 | /** 37 | * Constructors, the semantics are almost the same as sharp::Future, 38 | * except for the copy constructors, in which the shared state is copied 39 | */ 40 | SharedFuture() noexcept; 41 | SharedFuture(SharedFuture&&) noexcept; 42 | SharedFuture(const SharedFuture&); 43 | SharedFuture(Future>&&); 44 | SharedFuture(Future&&) noexcept; 45 | 46 | /** 47 | * Assignment operators 48 | */ 49 | SharedFuture& operator=(const SharedFuture&) = default; 50 | SharedFuture& operator=(SharedFuture&&) noexcept = default; 51 | 52 | /** 53 | * Returns a const reference to the value in the shared state, if there is 54 | * no value then the function blocks, if there is no shared state then an 55 | * exception is thrown 56 | */ 57 | const Type& get() const; 58 | 59 | /** 60 | * The same as sharp::Future 61 | */ 62 | bool valid() const noexcept; 63 | void wait() const; 64 | bool is_ready() const noexcept; 65 | 66 | /** 67 | * The same as sharp::Future::then but instead the function/functor passed 68 | * in should accept a shared_future by value 69 | */ 70 | template * 72 | = nullptr> 73 | auto then(Func&& func) -> Future; 74 | template * 76 | = nullptr> 77 | auto then(Func&& func) -> decltype(func(*this)); 78 | 79 | /** 80 | * Make friends with the promise class 81 | */ 82 | template 83 | friend class sharp::Promise; 84 | 85 | /** 86 | * Make friends with all instantiations of the future class, this is 87 | * needed because there are a lot of methods here which rely on futures 88 | * that have been instantiated differently from the current instantiation 89 | * of the future class. And it is nice to be able to call private methods 90 | * on these other instantiations as well 91 | */ 92 | template 93 | friend class sharp::SharedFuture; 94 | 95 | /** 96 | * Make friends with the SharedFuture class 97 | */ 98 | template 99 | friend class sharp::Future; 100 | 101 | /** 102 | * Make friends with the ComposableFuture class because that uses private 103 | * members of this class 104 | */ 105 | template 106 | friend class sharp::detail::ComposableFuture; 107 | 108 | private: 109 | 110 | void check_shared_state() const; 111 | template 112 | auto then_impl(Func&& func) -> Future; 113 | 114 | std::shared_ptr> shared_state; 115 | }; 116 | 117 | } // namespace sharp 118 | 119 | #include 120 | -------------------------------------------------------------------------------- /Future/SharedFuture.ipp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace sharp { 10 | 11 | namespace detail { 12 | 13 | /** 14 | * Assigns a shared future from the promise 15 | */ 16 | template 17 | void move_from_promise(Promise& promise, SharedFuture& future) { 18 | future = promise.get_future().share(); 19 | } 20 | 21 | } // namespace detail 22 | 23 | template 24 | SharedFuture::SharedFuture() noexcept {}; 25 | 26 | template 27 | SharedFuture::SharedFuture(SharedFuture&& other) noexcept 28 | : shared_state{std::move(other.shared_state)} {} 29 | 30 | template 31 | SharedFuture::SharedFuture(const SharedFuture& other) 32 | : shared_state{other.shared_state} {} 33 | 34 | template 35 | SharedFuture::SharedFuture(Future&& other) noexcept 36 | : shared_state{std::move(other.shared_state)} {} 37 | 38 | template 39 | SharedFuture::SharedFuture(Future>&& other) { 40 | 41 | other.check_shared_state(); 42 | 43 | // make a promise future pair, and then assign the shared state from the 44 | // future to the shared state of the current shared future 45 | auto promise = sharp::Promise{}; 46 | this->shared_state = promise.get_future().shared_state; 47 | 48 | // release the shared state for the other future 49 | auto deferred = defer_guard([&other]() { 50 | other.shared_state.reset(); 51 | }); 52 | 53 | // when the future is completed, assign the shared 54 | other.then([promise = std::move(promise)](auto future) { 55 | 56 | // if the future contains an exception then propagate that to this 57 | if (future.shared_state->contains_exception()) { 58 | promise.set_exception(future.shared_state->get_exception_ptr()); 59 | return; 60 | } 61 | 62 | // get the shared future from the future 63 | auto shared_future = future.get(); 64 | 65 | // if the shared future is not valid then store an exception in this 66 | if (!future.shared_state->get_value().valid()) { 67 | auto exc = FutureError{FutureErrorCode::broken_promise}; 68 | promise.set_exception(std::make_exception_ptr(exc)); 69 | return; 70 | } 71 | 72 | // hook into the shared future and copy the value when that shared 73 | // future is completed 74 | shared_future.shared_state->add_callback( 75 | [promise = std::move(promise)](auto& state) { 76 | if (state.contains_exception()) { 77 | promise.set_exception(state.get_exception_ptr()); 78 | } else { 79 | promise.set_value(state.get_copy()); 80 | } 81 | }); 82 | }); 83 | } 84 | 85 | template 86 | const Type& SharedFuture::get() const { 87 | this->check_shared_state(); 88 | this->shared_state->wait(); 89 | return this->shared_state->get_copy(); 90 | } 91 | 92 | template 93 | bool SharedFuture::valid() const noexcept { 94 | return static_cast(this->shared_state); 95 | } 96 | 97 | template 98 | void SharedFuture::wait() const { 99 | this->check_shared_state(); 100 | this->shared_state->wait(); 101 | } 102 | 103 | template 104 | bool SharedFuture::is_ready() const noexcept { 105 | this->check_shared_state(); 106 | return this->shared_state.is_ready(); 107 | } 108 | 109 | template 110 | void SharedFuture::check_shared_state() const { 111 | if (!this->valid()) { 112 | throw FutureError{FutureErrorCode::no_state}; 113 | } 114 | } 115 | 116 | template 117 | template *> 119 | auto SharedFuture::then(Func&& func) 120 | -> Future { 121 | return this->detail::ComposableFuture>::then( 122 | std::forward(func)) 123 | .via(this->get_executor()); 124 | } 125 | 126 | template 127 | template *> 129 | auto SharedFuture::then(Func&& func) 130 | -> decltype(func(*this)) { 131 | using T = typename std::decay_t::value_type; 132 | return Future{this->detail::ComposableFuture>::then( 133 | std::forward(func))} 134 | .via(this->get_executor()); 135 | } 136 | 137 | template 138 | SharedFuture Future::share() { 139 | this->check_shared_state(); 140 | return SharedFuture{std::move(*this)}; 141 | } 142 | 143 | } // namespace sharp 144 | -------------------------------------------------------------------------------- /Future/detail/Future-pre.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Future-pre.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * This file contains boring metaprogramming facilities that are used by the 6 | * public interface of future 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | 15 | namespace sharp { 16 | 17 | /** 18 | * Forward declaration of Future for metaprogramming purposes 19 | */ 20 | template 21 | class Future; 22 | template 23 | class SharedFuture; 24 | 25 | namespace detail { 26 | 27 | /** 28 | * Enables the template in whatever way this might be used if the return 29 | * type of the functor is a future 30 | */ 31 | template 32 | using EnableIfReturnsFuture = std::enable_if_t< 33 | sharp::IsInstantiationOf_v< 34 | decltype(std::declval()(std::declval>())), 35 | Future>>; 36 | template 37 | using EnableIfDoesNotReturnFuture = std::enable_if_t< 38 | !sharp::IsInstantiationOf_v< 39 | decltype(std::declval()(std::declval>())), 40 | Future>>; 41 | template 42 | using EnableIfReturnsFutureShared = std::enable_if_t< 43 | sharp::IsInstantiationOf_v< 44 | decltype(std::declval()(std::declval>())), 45 | Future>>; 46 | template 47 | using EnableIfDoesNotReturnFutureShared = std::enable_if_t< 48 | !sharp::IsInstantiationOf_v< 49 | decltype(std::declval()(std::declval>())), 50 | Future>>; 51 | 52 | } // namespace detail 53 | 54 | } // namespace sharp 55 | -------------------------------------------------------------------------------- /Future/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | deps = [ 4 | "//Future:Future", 5 | "//Threads:Threads", 6 | ], 7 | srcs = [ 8 | "test.cpp", 9 | ] 10 | ) 11 | -------------------------------------------------------------------------------- /Invoke/Invoke.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Invoke.hpp 3 | * @author Aaryaman Sagar 4 | */ 5 | 6 | #pragma once 7 | 8 | namespace sharp { 9 | 10 | /** 11 | * @function invoke 12 | * 13 | * This function allows the user to invoke any callable with arguments in any 14 | * order (with the relative ordering maintained) 15 | * 16 | * Further it natively supports optional types and allows the user to skip 17 | * passing those in to the invocation entirely. It also supports 18 | * one-from-many in the form of variant types, where only one of the many list 19 | * of options can be passed in 20 | */ 21 | template 22 | decltype(auto) invoke(Func&& func, Args&&... args); 23 | 24 | } // namespace sharp 25 | -------------------------------------------------------------------------------- /Invoke/Invoke.ipp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace sharp { 10 | 11 | /** 12 | * Placeholder optional and variant classes till std::optional and 13 | * std::variant are available 14 | */ 15 | template 16 | class OptionalType {}; 17 | template 18 | class VariantType {}; 19 | 20 | namespace invoke_detail { 21 | 22 | } // namespace invoke_detail 23 | 24 | template 26 | decltype(auto) invoke(Func&& func, Args&&... args) { 27 | 28 | // store the args in a tuple 29 | auto args = std::forward_as_tuple(std::forward(args)...); 30 | 31 | // then forward the args to the implementation 32 | return invoke_detail::invoke_impl(std::forward(func), 33 | std::make_index_sequence{}, std::move(args)); 34 | } 35 | 36 | } // namespace sharp 37 | -------------------------------------------------------------------------------- /Invoke/README.md: -------------------------------------------------------------------------------- 1 | `Invoke` Super charged function calls 2 | -------- 3 | 4 | ```c++ 5 | void func(int a, double d, const std::string& str) { 6 | cout << a << " " << d << " " << str << endl; 7 | } 8 | 9 | int main() { 10 | sharp::invoke(func, 1, 1.2, "something"); 11 | sharp::invoke(func, 1.2, 1, "something"); 12 | sharp::invoke(func, "something", 1, 1.2); 13 | sharp::invoke(func, "something", 1.2, 1); 14 | sharp::invoke(func, 1.2, "something", 1); 15 | sharp::invoke(func, 1, "something", 1.2); 16 | } 17 | ``` 18 | 19 | This utility can be used to match a function call to any of its arguments in 20 | any order, the user does not have to know the order in which functions are 21 | expected to be delivered. They only need to know which arguments to pass 22 | 23 | #### Named arguments 24 | 25 | ```c++ 26 | STRONG_TYPEDEF(int, Port); 27 | STRONG_TYPEDEF(std::string, Host); 28 | 29 | auto make_server(Port port, Host host) { 30 | return Server{port, host}; 31 | } 32 | 33 | int main() { 34 | auto server = sharp::invoke(make_server, Host{"localhost"}, Port{80}); 35 | server.run(); 36 | } 37 | ``` 38 | 39 | This lets you create a list of arguments that are distinguished by strong 40 | typedefs. Strong typedefs while not supported by the language are mimicked by 41 | the macro `STRONG_TYPEDEF`, which is similar to boost's `BOOST_STRONG_TYPEDEF` 42 | but supports more modern C++ features 43 | 44 | #### Optional arguments 45 | 46 | ```c++ 47 | STRONG_TYPEDEF(int, Port); 48 | STRONG_TYPEDEF(std::string, Host); 49 | 50 | auto make_server(std::optional port, std::optional host) { 51 | return Server{port.value_or(80), host.value_or("localhost")}; 52 | } 53 | 54 | int main() { 55 | auto server = sharp::invoke(make_server, Port{8080}); 56 | server.run(); 57 | } 58 | ``` 59 | 60 | `sharp::invoke` has built in support for optional types, so if an argument is 61 | not supplied, the optional type will be used to provide a default value. 62 | 63 | #### Variant arguments 64 | 65 | ```c++ 66 | STRONG_TYPEDEF(int, Port); 67 | STRONG_TYPEDEF(std::string, Host); 68 | 69 | auto make_server(std::optional port, std::variant options) {} 70 | 71 | int main() { 72 | // valid 73 | sharp::invoke(make_server, Port{80}, 2); 74 | sharp::invoke(make_server, Port{80}, 3.2); 75 | 76 | // invalid, will not compile 77 | sharp::invoke(make_server, 2.1, 2); 78 | sharp::invoke(make_server, Port{80}, 2, 2.1); 79 | } 80 | ``` 81 | 82 | `std::variant` can be stressed to emphasize that only one of many arguments is 83 | required, and the library will `static_assert` to ensure that's the case 84 | -------------------------------------------------------------------------------- /Invoke/something.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using std::cout; 6 | using std::endl; 7 | 8 | int main() { 9 | auto t_index_one = std::type_index{typeid(int)}; 10 | auto t_index_two = std::type_index{typeid(int)}; 11 | // auto t_index_one = 1; 12 | // auto t_index_two = 1; 13 | volatile auto sum = 0; 14 | 15 | for (auto i = 0; i < 1e9; ++i) { 16 | if (t_index_one == t_index_two) { 17 | ++sum; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MoveInto/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "MoveInto", 3 | header_namespace = "sharp/MoveInto", 4 | exported_headers = [ 5 | "MoveInto.ipp", 6 | "MoveInto.hpp", 7 | ], 8 | visibility = [ 9 | "PUBLIC", 10 | ], 11 | 12 | tests = [ 13 | "//MoveInto/test:test", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /MoveInto/MoveInto.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MoveInto.hpp 3 | * @author Aaryaman Sagar 4 | */ 5 | 6 | #pragma once 7 | 8 | namespace sharp { 9 | 10 | template 11 | class MoveInto { 12 | public: 13 | 14 | /** 15 | * The default constructor and any constructor that constructs this from 16 | * another instance of MoveInto is deleted, an instance of MoveInto can 17 | * only be constructed by moving an object of type T 18 | * 19 | * Also delete the copy convert constructor, copies should not be passed 20 | * here 21 | */ 22 | MoveInto() = delete; 23 | MoveInto(const MoveInto&) = delete; 24 | MoveInto(const T&) = delete; 25 | 26 | /** 27 | * The move constructor needs to be defaulted in C++14 because prvalue 28 | * elision is not mandatory and code will not compile if an rvalue is used 29 | * to construct a MoveInto instance in a function parameter 30 | */ 31 | MoveInto(MoveInto&&) = default; 32 | 33 | /** 34 | * The convert move constructor takes an rvalue reference to a T and moves 35 | * it into the private instance, forcing a move 36 | */ 37 | MoveInto(T&& in); 38 | 39 | /** 40 | * The destructor is defaulted 41 | */ 42 | ~MoveInto() = default; 43 | 44 | /** 45 | * Pointer like methods to use this like a pointer 46 | */ 47 | T& operator*() &; 48 | const T& operator*() const &; 49 | T&& operator*() &&; 50 | const T&& operator*() const &&; 51 | T* operator->(); 52 | const T* operator->() const; 53 | 54 | private: 55 | T instance; 56 | }; 57 | 58 | } // namespace sharp 59 | 60 | #include 61 | -------------------------------------------------------------------------------- /MoveInto/MoveInto.ipp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace sharp { 8 | 9 | template 10 | MoveInto::MoveInto(T&& in) : instance{std::move(in)} {} 11 | 12 | template 13 | T& MoveInto::operator*() & { 14 | return this->instance; 15 | } 16 | 17 | template 18 | const T& MoveInto::operator*() const & { 19 | return this->instance; 20 | } 21 | 22 | template 23 | T&& MoveInto::operator*() && { 24 | return std::move(this->instance); 25 | } 26 | 27 | template 28 | const T&& MoveInto::operator*() const && { 29 | return std::move(this->instance); 30 | } 31 | 32 | template 33 | T* MoveInto::operator->() { 34 | return std::addressof(this->instance); 35 | } 36 | 37 | template 38 | const T* MoveInto::operator->() const { 39 | return std::addressof(this->instance); 40 | } 41 | 42 | } // namespace sharp 43 | -------------------------------------------------------------------------------- /MoveInto/README.md: -------------------------------------------------------------------------------- 1 | `MoveInto` Self documenting move only parameter passing 2 | ---------- 3 | 4 | This is motivated by the following example related to ambiguity with respect 5 | to rvalue parameter passing. For example, assume that `foo` and `bar` are two 6 | functions that expect a `Something` instance to be moved into them. The 7 | functions can look something like this 8 | 9 | ```c++ 10 | void foo(Something&& something) { ... } 11 | void bar(Something something) { ... } 12 | ``` 13 | 14 | Now user code can look like this, with the problems highlighted in the 15 | comments 16 | 17 | ```c++ 18 | auto something = Something{}; 19 | foo(std::move(something)); 20 | 21 | // has something been moved from or is it in the same state it was in before 22 | // the function call? 23 | 24 | // oops accidentally copied it into the function 25 | bar(something); 26 | ``` 27 | 28 | Wrapping classes in `MoveInto` makes it clear to the caller that any object 29 | being called will be moved into the function. Anything else will just not 30 | work 31 | 32 | ```c++ 33 | void foo(MoveInto something) { ... } 34 | void bar(MoveInto something) { ... } 35 | 36 | int main() { 37 | auto something = Something{}; 38 | foo(std::move(something)); 39 | 40 | // something is in its moved from state 41 | 42 | // won't compile because the function expects a move 43 | bar(something); 44 | } 45 | ``` 46 | 47 | This can even be used to create variables that should only be moved into from 48 | other variables 49 | 50 | ```c++ 51 | class Wrapper { 52 | public: 53 | MoveInto something; 54 | }; 55 | ``` 56 | 57 | ### Performance 58 | 59 | There is no performance cost to using `MoveInto`, it's a simple wrapper that 60 | holds an instance of the type it was templated on. The only thing to consider 61 | here as opposed to regular parameter passing is that since it is not an 62 | aggregate, prvalues will not be elided into the function parameters, they will 63 | be moved 64 | 65 | ### Object access 66 | 67 | `MoveInto` instances behave like pointers and the internal stored values can 68 | be accessed with the dereference (`*`) operator as well as the arrow (`->`) 69 | operator, similar to `std::optional` 70 | -------------------------------------------------------------------------------- /MoveInto/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//MoveInto:MoveInto", 8 | "//Traits:Traits", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /MoveInto/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace { 11 | 12 | int* foo(sharp::MoveInto> ptr) { 13 | return ptr->get(); 14 | } 15 | 16 | template > 17 | class CheckCopyDisallowed : public std::integral_constant {}; 18 | template 19 | class CheckCopyDisallowed{std::declval()})>> 21 | : public std::integral_constant {}; 22 | 23 | } // namespace 24 | 25 | TEST(MoveInto, Basic) { 26 | auto u_ptr = std::make_unique(1); 27 | sharp::MoveInto> ptr{std::move(u_ptr)}; 28 | EXPECT_EQ(**ptr, 1); 29 | } 30 | 31 | TEST(MoveInto, BasicCallFunction) { 32 | auto u_ptr = std::make_unique(1); 33 | auto ptr = u_ptr.get(); 34 | auto result = foo(std::move(u_ptr)); 35 | EXPECT_EQ(ptr, result); 36 | } 37 | 38 | TEST(MoveInto, CheckCopyNotAllowed) { 39 | EXPECT_TRUE((CheckCopyDisallowed>::value)); 40 | EXPECT_TRUE((CheckCopyDisallowed>::value)); 41 | } 42 | -------------------------------------------------------------------------------- /Mutable/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Mutable", 3 | header_namespace = "sharp/Mutable", 4 | exported_headers = [ 5 | "Mutable.pre.hpp", 6 | "Mutable.hpp", 7 | ], 8 | deps = [ 9 | "//Traits:Traits", 10 | ], 11 | visibility = [ 12 | "PUBLIC", 13 | ], 14 | 15 | tests = [ 16 | "//Mutable/test:test", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /Mutable/Mutable.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Mutable.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * A small expressive utility to get around the fact that mutable is not a 6 | * part of the type system, with some added benefits 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace sharp { 18 | 19 | /** 20 | * @class Mutable 21 | * 22 | * A utility to allow creation of mutable objects, more flexible than the 23 | * built in mutable keyword and is clearer in a lot of cases, for example 24 | * 25 | * auto mutexes = std::unordered_map{}; 26 | * // fill map 27 | * 28 | * // read thread, cannot modify the map 29 | * void foo(const std::unordered_map& mutexes) { 30 | * auto mutex = mutexes.at(1); 31 | * mutex.lock(); // error 32 | * } 33 | * 34 | * This can be easily fixed by making the mutex member mutable, which it 35 | * should be 36 | * 37 | * auto mutexes = std::unordered_map>{}; 38 | * 39 | * There are some other cases where this comes in handy, like making a set of 40 | * mutable instances, which are logically const (with respect to the hashing 41 | * or comparison values) but can be changed under the hood 42 | * 43 | * The mutable class when made a base class also applies the empty base 44 | * optimization so it can be used to sub in implementations when needed 45 | * efficiently 46 | */ 47 | template 48 | class Mutable : public mutable_detail::MutableBase { 49 | public: 50 | static_assert(!std::is_const::value, "pls"); 51 | static_assert(!std::is_reference::value, "no references allowed"); 52 | 53 | /** 54 | * Default the constructors 55 | */ 56 | Mutable() = default; 57 | Mutable(Mutable&&) = default; 58 | Mutable(const Mutable&) = default; 59 | Mutable& operator=(Mutable&&) = default; 60 | Mutable& operator=(const Mutable&) = default; 61 | 62 | /** 63 | * Implicit constructors from instances of type Type, made implicit 64 | * intentionally for convenience. This allows both regular list 65 | * initialization in function parameters, etc as well as the nice list 66 | * initialization syntax, so things like std::in_place can be avoided 67 | * 68 | * auto var = sharp::Mutable>{{1, 2, 3, 4}}; 69 | * 70 | * This puts the initializer {1, 2, 3, 4} as if it were used to initialize 71 | * the contained std::vector 72 | */ 73 | Mutable(const Type& instance) 74 | : mutable_detail::MutableBase{instance} {} 75 | Mutable(Type&& instance) 76 | : mutable_detail::MutableBase{std::move(instance)} {} 77 | 78 | /** 79 | * Assignment operators from instances of type Type, can be used 80 | * for implicit construction like above 81 | */ 82 | Mutable& operator=(const Type& instance) { 83 | this->get() = instance; 84 | return *this; 85 | } 86 | Mutable& operator=(Type&& instance) { 87 | this->get() = std::move(instance); 88 | return *this; 89 | } 90 | 91 | /** 92 | * Returns a reference to the contained object, note that this can be a 93 | * problem as with all monadic wrappers when used in rvalue mode, because 94 | * lifetime of a dereferenced value type is not extended when bound to an 95 | * rvalue reference or a const lvalue reference. For example 96 | * 97 | * auto&& value = *sharp::Mutable{}; 98 | * 99 | * Here `value` will be a dangling reference becasue the lifetime of the 100 | * Mutable object would have ended after the assignment. This leads to 101 | * issues, and is even possible with language constructs like the range 102 | * based for loop 103 | * 104 | * for (auto value : *sharp::Mutable>{{{1, 2, 3}}}) { 105 | * cout << value << endl; 106 | * } 107 | * 108 | * Here the loop will be iterating over an expired value. This is an 109 | * unfortunate consequence of the semantics of range based for loops. The 110 | * above expands to 111 | * 112 | * auto&& __range = *sharp::Mutable>{{{1, 2, 3}}}; 113 | * for (auto __it = __range.begin(); it != __range.end(); ++__it) { 114 | * auto value = *it; 115 | * cout << value << endl; 116 | * } 117 | * 118 | * And this has the same problem as the rvalue reference assignment above. 119 | * The reference is dangling. Use with care 120 | */ 121 | Type& get() const & { 122 | return mutable_detail::MutableBase::get(); 123 | } 124 | Type&& get() const && { 125 | return std::move(this->get()); 126 | } 127 | 128 | /** 129 | * Pointer like functions to make this behave like a pointer 130 | */ 131 | Type& operator*() const & { 132 | return this->get(); 133 | } 134 | Type&& operator*() const && { 135 | return std::move(this->get()); 136 | } 137 | Type* operator->() const { 138 | return std::addressof(this->get()); 139 | } 140 | }; 141 | 142 | } // namespace sharp 143 | -------------------------------------------------------------------------------- /Mutable/Mutable.pre.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Mutable.pre.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * Contains a small base class utility to allow EBO with types, and also work 6 | * with primitive types 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | 15 | namespace sharp { 16 | namespace mutable_detail { 17 | 18 | /** 19 | * Concepts(ish) 20 | */ 21 | /** 22 | * Tell whether a class is empty or not 23 | */ 24 | template 25 | using EnableIfNotEmpty = std::enable_if_t::value>; 26 | 27 | template > 28 | class MutableBase : public Type { 29 | public: 30 | Type& get() const { 31 | // cast away the constness of an empty base because it has no 32 | // state that can be used to cause UB, method calls on it are safe 33 | return const_cast(static_cast(*this)); 34 | } 35 | }; 36 | 37 | template 38 | class MutableBase> { 39 | public: 40 | Type& get() const { 41 | return instance; 42 | } 43 | 44 | mutable Type instance; 45 | }; 46 | 47 | } // namespace mutable_detail 48 | } // namespace sharp 49 | -------------------------------------------------------------------------------- /Mutable/README.md: -------------------------------------------------------------------------------- 1 | `Mutable` A library for mutability 2 | --------- 3 | 4 | A library utility for mutability, this has several advantages over mutability 5 | from the langauge level, which is not built into the type system 6 | (distinguishing it from specifiers like `const` and `volatile`). This leads 7 | to some limitations. For example 8 | 9 | ```c++ 10 | auto mutexes = std::unordered_map{}; 11 | // fill map 12 | 13 | // read thread, cannot modify the map 14 | void foo(const std::unordered_map& mutexes) { 15 | auto& mutex = mutexes.at(1); 16 | mutex.lock(); // error 17 | } 18 | ``` 19 | 20 | The above does not work because there is no way to enforce mutability into the 21 | type system and have it carry through a library's API. The solution to the 22 | above is simple 23 | 24 | ```c++ 25 | auto mutexes = std::unordered_map>{}; 26 | 27 | // read thread, cannot modify the map 28 | void foo(const std::unordered_map& mutexes) { 29 | auto& mutex = mutexes.at(1); 30 | mutex->lock(); 31 | } 32 | ``` 33 | 34 | This carries through the type system and enforces mutability right at the 35 | source wherever it is required 36 | 37 | Once the utility is built into the type system, it also has other uses. For 38 | example, it can also help in template metaprogramming when you want to 39 | optionally include functionality and state into a class without impacting its 40 | size and functionality when it's not needed as a base class 41 | 42 | ```c++ 43 | template 44 | class Something : public sharp::Mutable> { ... }; 45 | ``` 46 | -------------------------------------------------------------------------------- /Mutable/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Mutable:Mutable", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Mutable/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace sharp { 5 | 6 | TEST(Mutable, BasicTest) { 7 | const auto mutable_integer = sharp::Mutable{1}; 8 | EXPECT_EQ(*mutable_integer, 1); 9 | *mutable_integer = 2; 10 | EXPECT_EQ(*mutable_integer, 2); 11 | } 12 | 13 | } // namespace sharp 14 | -------------------------------------------------------------------------------- /Mutex/README.md: -------------------------------------------------------------------------------- 1 | `Mutex` Very ***very*** small transactional mutexes 2 | ------- 3 | 4 | ```c++ 5 | auto mutex = sharp::Mutex<>{}; 6 | 7 | mutex.lock(); 8 | ... 9 | mutex.unlock(); 10 | ``` 11 | 12 | This module contains two locks - a spinlock (`sharp::Spinlock`) and a full 13 | featured adaptive sleeping lock (`sharp::Mutex`). Both locks take up one 14 | bit and two bits respectively. 15 | 16 | C++ requires that objects are at least a single byte wide, to overcome this 17 | limitation the mutex objects contain an embedded unsigned integral value that 18 | can be used for any other purpose 19 | 20 | ```c++ 21 | auto mutex = sharp::Mutex{}; 22 | 23 | auto data = mutex.lock_fetch(); 24 | cout << *data << endl; 25 | data.unlock(); 26 | ``` 27 | 28 | The mutex takes up the two most significant bits of the storage allocated for 29 | it and the rest is used up by the coexistent scalar 30 | 31 | The API ensures that users only make safe writes to the embedded integer by 32 | following an RAII based access pattern and safely committing to the underlying 33 | storage on destruction. If the user tries to change the spinlock state, the 34 | program will try and raise an exception or crash with a helpful error message 35 | 36 | ```c++ 37 | auto mutex = sharp::Mutex{}; 38 | 39 | auto data = mutex.lock_fetch(); 40 | *data = 0b11000000; 41 | data.unlock(); 42 | ``` 43 | 44 | Here the API will throw saying that the user accidentally overflowed the 45 | storage allocated for the mutex 46 | -------------------------------------------------------------------------------- /Optional/BUCK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aary/sharp/e96b0c69cb4c6296b1f56585a958cd85a7a90ab6/Optional/BUCK -------------------------------------------------------------------------------- /Optional/Optional.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Optional.hpp 3 | * @author Aaryaman Sagar (rmn100@gmail.com) 4 | * 5 | * This file contains an Optional data type which differs from popular 6 | * implementations in one important way - most implementaions of an optional 7 | * are similar to std::pair with the bool representing whether the 8 | * object is valid or not. This module allows the user to use an optional 9 | * type without any extra memory overhead. 10 | */ 11 | 12 | #pragma once 13 | 14 | namespace sharp { 15 | 16 | /* 17 | * Implementation specific things that users should not try and use 18 | */ 19 | namespace detail { 20 | 21 | /** 22 | * A tag that implies no special null policy for the type contained in the 23 | * optional. When this tag is passed in as the second template argument to 24 | * the Optional type the implementation selects the implementation of the 25 | * Optional type with an extra boolean taking up space. 26 | */ 27 | template 28 | class NoNullPolicy{}; 29 | 30 | /** 31 | * The Optional base class that contains member methods for things like 32 | * emplacing the object into the container at runtime. The move constructors 33 | * and things like that. The Optional implementations inherit from this to 34 | * provide further functionality. 35 | * 36 | * This base class does not do anything that make the "Optional" special. It 37 | * is just meant to take care of the placement new-ing 38 | */ 39 | template 40 | class OptionalBase; 41 | 42 | /** 43 | * Partial specialization for when the null policy is actually given. This 44 | * specialization is optimized for space and uses the null policy to dictate 45 | * whether the object contained in the optional is null or not. For example 46 | * the null policy may be something like the following 47 | * 48 | * struct NullPolicy : public NullPolicyBase { 49 | * static constexpr int NULL_VALUE{-1}; 50 | * } 51 | * 52 | * This dictates that the null policy for the contained integer value is that 53 | * when the integer is a -1 the optional should be treated such that it has a 54 | * null value. 55 | * 56 | * What happens in the case where the optional receives a "null" value at 57 | * runtime? An exception is thrown with a strong exception guarantee to alert 58 | * the user of that a null value snuck in so that the user can undo his 59 | * illegal actions. 60 | * 61 | * To define your own null policy please read the documentation of 62 | * NullPolicyBase 63 | */ 64 | template 65 | class OptionalImpl; /* : public detail::OptionalBase; */ 66 | 67 | template 68 | class OptionalImpl>; 69 | /* : public detail::OptionalBase; */ 70 | 71 | } // namespace detail 72 | 73 | /** 74 | * The null policy base class that all user defined null policies should 75 | * extend from. For example 76 | * 77 | * struct UserDefinedNull : public NullPolicyBase { 78 | * static constexpr int NULL_VALUE{1}; 79 | * } 80 | */ 81 | template 82 | class NullPolicyBase; 83 | 84 | /** 85 | * @class Optional 86 | * 87 | * A template optional data type class that is used to represent optional 88 | * types in a type safe way. Either the value exists or does not exist. 89 | * 90 | * The object is not constructed into the optional until it is assigned a 91 | * value. No extra memory is used on the heap. All bookkeeping is done by 92 | * the type itself and on the stack 93 | */ 94 | template > 95 | class Optional; /* : public OptionalImpl; */ 96 | 97 | } // namespace sharp 98 | -------------------------------------------------------------------------------- /Optional/README.md: -------------------------------------------------------------------------------- 1 | `Optional` 2 | ---------- 3 | 4 | This module contains an optional module that extends the regular functionality 5 | provided by the standard library optional to include one more mode of 6 | operation - compact optionals. Compact optionals can be useful when there is 7 | a strict memory requirement, if and when there is a strict memory requirement, 8 | compact optionals use much less memory than the regular optional type. In 9 | particular they do not contain a boolean to denote whether a type exists or 10 | not and do not contain the padding associated with that boolean, which can at 11 | times be siginificant, for example 12 | 13 | ``` 14 | struct Something { 15 | bool boolean; 16 | std::int32_t four_byte_integer; 17 | }; 18 | ``` 19 | 20 | The memory footprint of a `Something` object is significantly bloated because 21 | of the extra boolean contained within it, the size of the struct needs to be 22 | aligned to a multiple of the largest type and as such the size degrades to 8 23 | bytes when only 5 bytes are needed. Compact optionals solve this problem by 24 | occupying only the amount of space needed to store the actual type, for 25 | example if an optional type was being built around an `std::int32_t` object 26 | then the compact optional would also occupy only 4 bytes. 27 | 28 | The caveat with compact optionals however is that there needs to be a special 29 | value reserved with the type that represents a "null" value, in other words a 30 | sentinal value needs to exist for the underlying data distribution. For 31 | example if it is known that the `std::int32_t` cannot contain any negative 32 | value, then the sentinal can be a `-1`, or in many cases a suitable value for 33 | the sentinal can simply be a `0`. 34 | -------------------------------------------------------------------------------- /OrderedContainer/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "OrderedContainer", 3 | header_namespace = "sharp/OrderedContainer", 4 | headers = [ 5 | "OrderedContainer.hpp", 6 | "OrderedContainer.ipp", 7 | ], 8 | exported_headers = [ 9 | "OrderedContainer.hpp", 10 | "OrderedContainer.ipp", 11 | ], 12 | deps = [ 13 | "//Traits:Traits", 14 | ], 15 | visibility = [ 16 | "PUBLIC", 17 | ], 18 | 19 | tests = [ 20 | "//OrderedContainer/test:test", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /OrderedContainer/README.md: -------------------------------------------------------------------------------- 1 | OrderedContainer 2 | ---------------- 3 | 4 | `OrderedContainer` contains an interface that is compiled on top of the 5 | existing interfaces for containers that allows them to be used in an ordered 6 | fashion, in the best way possible. So for example if the goal is to make a 7 | linked list that is sorted, then you can do the following 8 | 9 | ```c++ 10 | OrderedContainer> ordered_list; 11 | 12 | // insert values into the list 13 | ordered_list.insert(1); 14 | ordered_list.insert(0); 15 | ordered_list.insert(2); 16 | 17 | auto iter = ordered_list.find(1) 18 | if (iter != ordered_list.end()) { 19 | cerr << "Could not find a 1 in the container" << endl; 20 | } 21 | ``` 22 | 23 | The library also offers users a way to fetch the underlying container for its 24 | specific functionality like so 25 | 26 | ```c++ 27 | auto& inner_list = ordered_list.get(); 28 | static_assert(std::is_same< 29 | std::list, 30 | std::decay_t>::value, 31 | "OrderedContainer is incorrect, get() returns the wrong type"); 32 | ``` 33 | -------------------------------------------------------------------------------- /OrderedContainer/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//OrderedContainer:OrderedContainer", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /OrderedContainer/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace sharp; 14 | using namespace std; 15 | 16 | TEST(OrderedContainer, simple_test) { 17 | OrderedContainer, std::less> oc; 18 | oc.insert(0); 19 | oc.insert(1); 20 | oc.insert(2); 21 | EXPECT_NE(oc.find(0), oc.end()); 22 | EXPECT_EQ(*oc.find(0), 0); 23 | EXPECT_NE(oc.find(1), oc.end()); 24 | EXPECT_EQ(*oc.find(1), 1); 25 | EXPECT_NE(oc.find(2), oc.end()); 26 | EXPECT_EQ(*oc.find(2), 2); 27 | } 28 | 29 | vector generate_random_integers(int number_of_integers) { 30 | vector random_integers; 31 | random_integers.reserve(number_of_integers); 32 | std::srand(std::time(nullptr)); 33 | for (auto i = 0; i < number_of_integers; ++i) { 34 | random_integers.push_back(std::rand() % 100); 35 | } 36 | std::sort(random_integers.begin(), random_integers.end()); 37 | random_integers.erase( 38 | std::unique(random_integers.begin(), random_integers.end()), 39 | random_integers.end()); 40 | std::random_shuffle(random_integers.begin(), random_integers.end()); 41 | 42 | return random_integers; 43 | } 44 | 45 | TEST(OrderedContainer, vector_test) { 46 | OrderedContainer, std::less> oc; 47 | auto random_integers = generate_random_integers(10000); 48 | 49 | for (const auto ele : random_integers) { 50 | oc.insert(ele); 51 | } 52 | std::sort(random_integers.begin(), random_integers.end()); 53 | 54 | // make sure that all the elements are in the list, pop them one by one 55 | // out and then assert that the ranges are equal 56 | for (; !oc.empty();) { 57 | auto value_to_remove = random_integers.back(); 58 | random_integers.pop_back(); 59 | 60 | // erase from the ordered container 61 | auto iter = oc.find(value_to_remove); 62 | EXPECT_NE(iter, oc.end()); 63 | oc.erase(iter); 64 | 65 | EXPECT_TRUE(std::equal(oc.begin(), oc.end(), random_integers.begin())); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Overload/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Overload", 3 | header_namespace = "sharp/Overload", 4 | exported_headers = [ 5 | "Overload.hpp", 6 | "Overload.ipp", 7 | ], 8 | visibility = [ 9 | "PUBLIC", 10 | ], 11 | deps = [ 12 | "//Traits:Traits", 13 | ], 14 | 15 | tests = [ 16 | "//Overload/test:test", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /Overload/Overload.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Overload.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * A utility to help overloading lambda expressions 6 | */ 7 | 8 | #pragma once 9 | 10 | namespace sharp { 11 | 12 | /** 13 | * Overload pretty much any invocable 14 | * 15 | * class Something { 16 | * public: 17 | * std::string identity(std::string in) { return in; } 18 | * }; 19 | * 20 | * char foo(char ch) { return ch; } 21 | * 22 | * auto overloaded = sharp::overload( 23 | * [&](int a) { return a; }, 24 | * [&](double d) { return d; }, 25 | * foo, 26 | * Something::print); 27 | * 28 | * assert(overloaded(1) == 1); 29 | * assert(overloaded(2.1) == 2.1); 30 | * assert(overloaded('a') == 'a'); 31 | * assert(overloaded(Something{}, "string"s) == "string"s); 32 | * 33 | * This can be useful in several scenarios, can be used to implement double 34 | * dispatch, variant visiting, etc, for example 35 | * 36 | * auto variant = std::variant{1}; 37 | * std::visit(sharp::overload([](int) {}, [](double) {}), variant); 38 | * 39 | * This also contains the type lists of the functions it holds as a compile 40 | * time tuple typedef, so it can be used in various function introspection 41 | * methods, see Traits/detail/FunctionIntrospect.hpp for more details on how 42 | * the argument deduction works 43 | */ 44 | template 45 | auto overload(Funcs&&... funcs); 46 | 47 | } // namespace sharp 48 | 49 | #include 50 | -------------------------------------------------------------------------------- /Overload/README.md: -------------------------------------------------------------------------------- 1 | `Overload` Overload anything 2 | ---------- 3 | 4 | ```c++ 5 | char foo(char ch) { 6 | return ch; 7 | } 8 | 9 | int main() { 10 | auto overloaded = sharp::overload( 11 | [&](double d) { return d; }, 12 | [&](std::string str) { return str; }, 13 | foo); 14 | 15 | assert(overloaded(1.2) == 1.2 16 | assert(overloaded("something") == "something"); 17 | assert(overloaded('a') == 'a'); 18 | } 19 | ``` 20 | 21 | This can be useful and can be used to make code readable and maintainable with 22 | complex double dispatch and visitor patterns 23 | 24 | ```c++ 25 | void handle_int(int) {} 26 | void handle_double(double) {} 27 | 28 | int main() { 29 | // ... 30 | 31 | auto variant = std::variant{}; 32 | std::visit( 33 | sharp::overload([&](std::string&) {}, handle_int, handle_double), 34 | variant); 35 | } 36 | ``` 37 | 38 | ### Performance 39 | 40 | This utility has no runtime performance hit. The overload dispatching is done 41 | at compile time and the empty base optimizations are applied everywhere. The 42 | size of overloaded functor instances is also just a single byte when no 43 | captures are done. The memory cost for functions however is a single pointer 44 | for each function. 45 | 46 | However, there is a non trivial O(n) template instantiation overhead, where n 47 | are the number of functions or functors passed to the utility. 48 | -------------------------------------------------------------------------------- /Overload/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Overload:Overload", 8 | "//Utility:Utility", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /Portability/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Portability", 3 | header_namespace = "sharp/Portability", 4 | exported_headers = [ 5 | "cpp17.hpp", 6 | "detail/optional.hpp", 7 | ], 8 | visibility = [ 9 | "PUBLIC", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /Portability/cpp17.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file cpp17.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * Just includes other files which have definitions for features not yet 6 | * standard, when those features are standard this file should be removed 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | -------------------------------------------------------------------------------- /Proxy/Proxy.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Proxy.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * A generic proxy class that encapsulates state, exposes it as a 6 | * "smart pointer" and executes some logic on destruction 7 | * 8 | * This is a generic unqiue_ptr type of class that can be used even when the 9 | * objects are stored on the stack, unlike std::unique_ptr which requires 10 | * objecrs to be stored on the heap and used with an allocator 11 | * 12 | * This can also be used as a wrapper around pointer-like objects, in which 13 | * case the pointer operations will be forwarded to that pointer type 14 | */ 15 | 16 | #pragma once 17 | 18 | /** 19 | * @class Proxy 20 | * 21 | * A generic RAII wrapper class that associates state on construction with a 22 | * data member. This state is then exposed via a pointer like interface and 23 | * then released on destruction with an on exit callback 24 | * 25 | * The proxy class can wrap objects allocated inline as well as externally 26 | * through other smart pointers (possibly on the heap) 27 | * 28 | * auto proxy = sharp::make_proxy([] { return 1; }, [](int i) { log(i); }); 29 | * cout << "Value in the proxy is " << *proxy; 30 | * 31 | * auto proxy = sharp::make_proxy( 32 | * [] { return std::make_unique(1); }, 33 | * [](auto integer) { log(integer); }}; 34 | * cout << "Value in the proxy is " << *proxy << endl; 35 | * 36 | * This sort of pointer like operator->() and operator* might not be desirable 37 | * so the class allows you to fetch references to the underlying data item via 38 | * a get() method 39 | * 40 | * auto proxy = make_int_proxy(); 41 | * static_assert(std::is_same::value); 42 | * 43 | * auto proxy = make_unique_ptr_int_proxy(); 44 | * static_assert(std::is_same&>::value); 46 | * 47 | * This should be constructed with the make_proxy() method which accepts two 48 | * callables - one which returns the thing that is to be stored in the proxy a 49 | * callable that will be invoked when the proxy is destroyed 50 | * 51 | * To wrap references, the creater function object passed to make_proxy should 52 | * return an object wrapped in a reference_wrapper this class will take care 53 | * of unwrapping the reference 54 | * 55 | * auto proxy = sharp::make_proxy([] { return std::ref(a); }, [](auto&){}); 56 | * 57 | * Now the object being wrapped in the proxy will be a reference, and access 58 | * to it will be controlled through the same pointer like syntax 59 | */ 60 | template 61 | class Proxy {}; 62 | -------------------------------------------------------------------------------- /Range/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Range", 3 | header_namespace = "sharp/Range", 4 | headers = [ 5 | "Range.hpp", 6 | "Range.ipp", 7 | ], 8 | exported_headers = [ 9 | "Range.hpp", 10 | "Range.ipp", 11 | ], 12 | visibility = [ 13 | "PUBLIC", 14 | ], 15 | 16 | tests = [ 17 | "//Range/test:test", 18 | ], 19 | ) 20 | -------------------------------------------------------------------------------- /Range/README.md: -------------------------------------------------------------------------------- 1 | `Range` 2 | ------- 3 | 4 | A Pythonic range class for C++ 5 | 6 | ```C++ 7 | for (auto integer : range(0, 10)) { 8 | cout << integer << endl; 9 | } 10 | ``` 11 | 12 | Or use it to construct a range given a pair of iterators 13 | 14 | ```C++ 15 | auto v = std::vector{1, 2, 3}; 16 | for (auto integer : range(v.begin(), v.end())) { 17 | cout << integer << endl; 18 | } 19 | -------------------------------------------------------------------------------- /Range/Range.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Range.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * A simple range module like the one in Python, it can be used with the range 6 | * based for loop syntax and has the same semantics as the one in Python. 7 | * 8 | * Just for fun ¯\_(ツ)_/¯ 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | namespace sharp { 17 | 18 | /** 19 | * The range function that should be embedded in a range based for loop like 20 | * so 21 | * 22 | * for (auto ele : range(0, 10)) { ... } 23 | * 24 | * This will return a proxy that can support increments on its iterators till 25 | * the entire range has been covered, in the optimized builds, the extra 26 | * inefficiencies from creating objects and iterators will likely be optimized 27 | * away and the result will be suprisingly similar to a hand written for loop 28 | */ 29 | template 30 | auto range(One begin, Two end); 31 | 32 | /** 33 | * Privates! 34 | */ 35 | namespace detail { 36 | 37 | /** 38 | * Forward declaration for the class Range that is going to be used as the 39 | * actual range module, this should never explicitly be instantiated and 40 | * hence is in the detail namespace 41 | */ 42 | template 43 | class Range { 44 | public: 45 | 46 | /** 47 | * Friend the factory function to create objects of this class 48 | */ 49 | friend auto sharp::range(One, Two); 50 | 51 | template 52 | class Iterator { 53 | public: 54 | 55 | /** 56 | * Iterator categories, STL algorithms dont work on some 57 | * implementations without these 58 | */ 59 | using difference_type = int; 60 | using value_type = IncrementableType; 61 | using pointer = std::add_pointer_t; 62 | using reference = std::add_lvalue_reference_t; 63 | using iterator_category = std::forward_iterator_tag; 64 | 65 | /** 66 | * The constructor for the iterator, adds the incrementable object 67 | * as a member variable 68 | */ 69 | Iterator(IncrementableType); 70 | 71 | /** 72 | * Increment operator for the iterator 73 | */ 74 | Iterator& operator++(); 75 | 76 | /** 77 | * Not equals operator 78 | */ 79 | template 80 | bool operator!=(Iterator other) const; 81 | 82 | /** 83 | * Return the thing that the iterator points to by value 84 | */ 85 | decltype(auto) operator*() const; 86 | 87 | private: 88 | IncrementableType incrementable; 89 | }; 90 | 91 | /** 92 | * Begin and end iterators for the range 93 | */ 94 | Iterator begin() const; 95 | Iterator end() const; 96 | 97 | private: 98 | /** 99 | * Private constructor for this class, can be constructed only with 100 | * the range() function defined below 101 | */ 102 | explicit Range(One, Two); 103 | 104 | /** 105 | * The members to keep track of what the starting and ending points 106 | * were, these cannot be compiled away with constexpr template 107 | * varaibles because there can be times when the user wants dynamic 108 | * variables as starting and ending points 109 | */ 110 | One first; 111 | Two last; 112 | }; 113 | 114 | } // namespace detail 115 | } // namespace sharp 116 | 117 | #include 118 | -------------------------------------------------------------------------------- /Range/Range.ipp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace sharp { 4 | 5 | template 6 | auto range(One begin, Two end) { 7 | return detail::Range{begin, end}; 8 | } 9 | 10 | /** 11 | * All the major implementation goes in the detail namespace 12 | */ 13 | namespace detail { 14 | 15 | /** 16 | * Concepts 17 | */ 18 | template 19 | using EnableIfIsIterator = std::enable_if_t::iterator_category, 21 | typename std::decay_t::iterator_category>::value>; 22 | 23 | /** 24 | * Compile time switch that returns the dereferneced version of the range 25 | * if the type is an iterator, whether or not the element is an iterator 26 | * is tested via the presence of the iterator_category trait 27 | */ 28 | template > 29 | struct Dereference { 30 | template 31 | static auto dereference(Incrementable&& i) { 32 | return i; 33 | } 34 | }; 35 | template 36 | struct Dereference> { 38 | template 39 | static decltype(auto) dereference(Incrementable&& i) { 40 | return *std::forward(i); 41 | } 42 | }; 43 | 44 | /** 45 | * Member functions of the main proxy Range 46 | */ 47 | template 48 | Range::Range(One begin_in, Two end_in) 49 | : first{begin_in}, last{end_in} {} 50 | 51 | template 52 | Range::Iterator Range:: begin() const { 53 | return Iterator{this->first}; 54 | } 55 | 56 | template 57 | Range::Iterator Range::end() const { 58 | return Iterator{this->last}; 59 | } 60 | 61 | 62 | /** 63 | * Member functions for the iterator type 64 | */ 65 | template 66 | template 67 | Range::Iterator::Iterator( 68 | IncrementableType incrementable_in) 69 | : incrementable{incrementable_in} {} 70 | 71 | template 72 | template 73 | decltype(auto) Range:: Iterator::operator*() 74 | const { 75 | return Dereference::dereference(this->incrementable); 76 | } 77 | 78 | template 79 | template 80 | Range::Iterator& 81 | Range::Iterator::operator++() { 82 | ++this->incrementable; 83 | return *this; 84 | } 85 | 86 | template 87 | template 88 | template 89 | bool Range::Iterator::operator!=( 90 | Iterator other) const { 91 | return this->incrementable != other.incrementable; 92 | } 93 | 94 | } // namespace detail 95 | 96 | } // namespace sharp 97 | -------------------------------------------------------------------------------- /Range/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Range:Range", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Range/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | using namespace sharp; 11 | 12 | TEST(Range, BasicTest) { 13 | 14 | // generate a vector with elements in [0, 10000) 15 | auto upper_limit = 10000; 16 | std::vector vec; 17 | vec.resize(upper_limit); 18 | std::generate(vec.begin(), vec.end(), [i = 0] () mutable { return i++; }); 19 | 20 | // now do the same thing with the range() function 21 | std::vector vec_two; 22 | for (auto i : range(0, upper_limit)) { 23 | vec_two.push_back(i); 24 | } 25 | 26 | EXPECT_TRUE(std::equal(vec.begin(), vec.end(), vec_two.begin())); 27 | } 28 | 29 | TEST(Range, ActualRange) { 30 | 31 | auto v = std::vector{1, 2, 3}; 32 | auto element_counter = 1; 33 | for (auto i : range(v.begin(), v.end())) { 34 | EXPECT_EQ(i, element_counter++); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Recursive/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Recursive", 3 | header_namespace = "sharp/Recursive", 4 | exported_headers = [ 5 | "Recursive.hpp", 6 | "Recursive.ipp", 7 | ], 8 | visibility = [ 9 | "PUBLIC", 10 | ], 11 | 12 | tests = [ 13 | "//Recursive/test:test", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /Recursive/Recursive.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Recursive.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * A utility for writing self referential lambdas 6 | */ 7 | 8 | #pragma once 9 | 10 | namespace sharp { 11 | 12 | /** 13 | * Recursive lambdas at 0 runtime cost 14 | * 15 | * There was a C++ paper about how to make lambdas recursive recently here 16 | * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0839r0.html. This 17 | * library presents an identical solution with no loss of generality with 18 | * almost a non existent syntax overhead. The usage is simple 19 | * 20 | * auto f = sharp::recursive([](auto& self, int start, int end, int sum) { 21 | * if (start == end) { 22 | * return sum; 23 | * } 24 | * 25 | * self(start + 1, end, sum + start); 26 | * }); 27 | * assert(f(0, 5, 0) == 10); 28 | * 29 | * The self parameter is a shim that can be used to refer to the closure 30 | * itself, and nothing more. It is similar to naming the lambda 31 | * 32 | * Lambdas wrapped with sharp::recursive can also decay to function pointers 33 | * if they do not capture any state. Just as regular lambdas 34 | * 35 | * using FPtr_t = int (*) (int, int, int); 36 | * FPtr_t f = sharp::recursive([](auto& self, int start, int end, int s) { 37 | * if (start == end) { 38 | * return s; 39 | * } 40 | * 41 | * f(start + 1, end, s + start); 42 | * }); 43 | * assert(f(0, 5, 0) == 10); 44 | * 45 | * But since lambdas deduce the return type of closure there are times when 46 | * you have to explicitly specify the return type. This manifests only when 47 | * the first return statement is seen after a recursive invocation, for 48 | * example if the above example was rewritten like so 49 | * 50 | * sharp::recursive([](auto& self, int start, int end, int sum) -> int { 51 | * if (start != end) { 52 | * self(start + 1, end, sum + 1); 53 | * } 54 | * return sum; 55 | * }); 56 | * 57 | * The lambda above needs the trailing return type as the first return 58 | * statement is seen after the first recursive invocation. This works just as 59 | * with regular functions. This is required to be compatible with the library 60 | * as it is impossible to be generic without imposing this restriction. If 61 | * this were not a requirement type introspection would not work with closures 62 | * that result from wrapping a lambda with sharp::recursive 63 | * 64 | * This thus presents a solution which is just as good as the library solution 65 | * without any loss of generality 66 | */ 67 | template 68 | auto recursive(Lambda&&); 69 | 70 | } // namespace sharp 71 | 72 | #include 73 | -------------------------------------------------------------------------------- /Recursive/Recursive.ipp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace sharp { 9 | namespace recursive_detail { 10 | 11 | template 12 | class RecursiveImpl : public Func { 13 | public: 14 | // is the function noexcept invocable 15 | template 16 | static constexpr const auto is_noexcept_invocable = noexcept( 17 | std::declval()(std::declval()...)); 18 | 19 | using Self_t = RecursiveImpl; 20 | 21 | template 22 | RecursiveImpl(F&& f) : Func{std::forward(f)} {} 23 | 24 | /** 25 | * The functions that are going to be invoked by users, these will 26 | * automatically cast to the lambda type and pasa a self parameter to 27 | * it, so users are not aware of the abstraction 28 | */ 29 | template 30 | auto operator()(Args&&... args) 31 | noexcept(is_noexcept_invocable) 32 | -> decltype(std::declval()( 33 | std::declval(), std::declval()...)) { 34 | return static_cast(*this)( 35 | *this, std::forward(args)...); 36 | } 37 | template 38 | auto operator()(Args&&... args) const 39 | noexcept(is_noexcept_invocable) 41 | -> decltype(std::declval()( 42 | std::declval(), 43 | std::declval()...)) { 44 | return static_cast(*this)( 45 | *this, std::forward(args)...); 46 | } 47 | }; 48 | 49 | } // namespace recursive_detail 50 | 51 | template 52 | auto recursive(Lambda&& lambda) { 53 | return recursive_detail::RecursiveImpl>{ 54 | std::forward(lambda)}; 55 | } 56 | 57 | } // namespace sharp 58 | -------------------------------------------------------------------------------- /Recursive/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Recursive:Recursive", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Recursive/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using std::cout; 9 | using std::endl; 10 | 11 | namespace sharp { 12 | 13 | namespace { 14 | class RecursiveTestCopyMoveConstruct { 15 | public: 16 | RecursiveTestCopyMoveConstruct() = default; 17 | RecursiveTestCopyMoveConstruct(const RecursiveTestCopyMoveConstruct&) { 18 | ++global_copies; 19 | } 20 | RecursiveTestCopyMoveConstruct(RecursiveTestCopyMoveConstruct&&) { 21 | ++global_moves; 22 | } 23 | 24 | static int global_copies; 25 | static int global_moves; 26 | }; 27 | int RecursiveTestCopyMoveConstruct::global_copies = 0; 28 | int RecursiveTestCopyMoveConstruct::global_moves = 0; 29 | } // namespace 30 | 31 | TEST(RecursiveTest, Basic) { 32 | auto sum = sharp::recursive([](auto& self, auto start, auto end, auto sum) { 33 | if (start == end) { 34 | return sum; 35 | } 36 | 37 | return self(start + 1, end, sum + start); 38 | }); 39 | 40 | EXPECT_EQ(sum(0, 5, 0), 10); 41 | } 42 | 43 | TEST(RecursiveTest, TestCapture) { 44 | auto sum = 0; 45 | auto sum_function = sharp::recursive([&](auto& self, auto start, auto end) { 46 | if (start == end) { 47 | return; 48 | } 49 | sum += start; 50 | self(start + 1, end); 51 | }); 52 | 53 | sum_function(0, 5); 54 | EXPECT_EQ(sum, 10); 55 | } 56 | 57 | TEST(RecursiveTest, TestCopyAndMoveOutside) { 58 | RecursiveTestCopyMoveConstruct::global_copies = 0; 59 | RecursiveTestCopyMoveConstruct::global_moves = 0; 60 | auto instance = RecursiveTestCopyMoveConstruct{}; 61 | 62 | auto func = sharp::recursive([=](auto&) -> void { 63 | std::ignore = instance; 64 | }); 65 | auto another = func; 66 | 67 | EXPECT_EQ(RecursiveTestCopyMoveConstruct::global_copies, 2); 68 | } 69 | 70 | TEST(RecursiveTest, TestCopyInsideClosure) { 71 | RecursiveTestCopyMoveConstruct::global_copies = 0; 72 | RecursiveTestCopyMoveConstruct::global_moves = 0; 73 | auto instance = RecursiveTestCopyMoveConstruct{}; 74 | auto integer = 1; 75 | 76 | sharp::recursive([&, instance](auto self) { 77 | std::ignore = instance; 78 | if (++integer == 3) { 79 | return; 80 | } 81 | self(); 82 | })(); 83 | 84 | EXPECT_EQ(integer, 3); 85 | 86 | // passing by value to itself should not have caused a move, but only 87 | // copies, as the copy is made at the point of invocation which has no 88 | // knowledge about the lifetime of the parameter itself 89 | // 90 | // one copy is made in the initial capture when the closure is created, 91 | // then the closure is moved into the wrapper in sharp::recursive(), and 92 | // then the two copies come from copy passing the wrapped closure into 93 | // itself 94 | EXPECT_EQ(RecursiveTestCopyMoveConstruct::global_copies, 3); 95 | EXPECT_EQ(RecursiveTestCopyMoveConstruct::global_moves, 1); 96 | } 97 | 98 | } // namespace sharp 99 | -------------------------------------------------------------------------------- /Settings/README.md: -------------------------------------------------------------------------------- 1 | `Settings` Event driven flags and settings 2 | ---------- 3 | 4 | ```c++ 5 | namespace settings = sharp::settings; 6 | namespace { 7 | auto config = settings::define("config", default_value, "a config"); 8 | } // namespace 9 | 10 | int main(int args, char** argv) { 11 | settings::init(args, argv); 12 | cout << "The value of config is " << *config << endl; 13 | } 14 | ``` 15 | 16 | And you can start the program like this 17 | ``` 18 | $ ./a.out --config 19 | ``` 20 | 21 | On program start the value of the config will be set to the value specified. 22 | This makes this library an easy way to define flags for your program 23 | 24 | Other than being just a flags library, this allows changing the configurations 25 | of the program at runtime 26 | ```c++ 27 | namespace settings = sharp::settings; 28 | namespace { 29 | auto config = settings::define("config", default_value, "a config"); 30 | } // namespace 31 | 32 | int main(int args, char** argv) { 33 | settings::init(args, argv); 34 | 35 | // use the config 36 | cout << "The value of config is " << *config << endl; 37 | 38 | // subscribe to changes to the configuration, add a callback to be invoked 39 | // via the executor ex 40 | config.via(ex).subscribe([](auto new_value) { 41 | cout << "The new value of config is " << new_value << endl; 42 | }); 43 | 44 | // publish changes to the config 45 | settings::publish("config", new_value); 46 | } 47 | ``` 48 | 49 | Which types can be set as settings? Any type that can be serialized and 50 | deserialized via `std::ostream` and `std::istream`. This means complex types 51 | like JSON can also be passed through the command line 52 | ```c++ 53 | namespace settings = sharp::settings; 54 | namespace { 55 | auto data = settings::define("data", settings::required, "some data"); 56 | } // namespace 57 | 58 | int main(int argc, char** argv) { 59 | settings::init(argc, argv); 60 | 61 | cout << data << endl; 62 | data.via(ex).subscribe([](auto new_value) { 63 | cout << "Got new data " << new_value << endl; 64 | }); 65 | 66 | settings::publish("data", "{'some key' : 'some value'}"); 67 | } 68 | ``` 69 | 70 | ``` 71 | $ ./a.out --data '{"some key" : "value"}' 72 | ``` 73 | -------------------------------------------------------------------------------- /Singleton/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Singleton", 3 | header_namespace = "sharp/Singleton", 4 | deps = [ 5 | "//Tags:Tags", 6 | "//Concurrent:Concurrent", 7 | "//Threads:Threads", 8 | ], 9 | srcs = [ 10 | "Singleton.cpp", 11 | ], 12 | headers = [ 13 | "Singleton.hpp", 14 | "Singleton.ipp", 15 | ], 16 | exported_headers = [ 17 | "Singleton.ipp", 18 | "Singleton.hpp", 19 | ], 20 | visibility = [ 21 | "PUBLIC", 22 | ], 23 | 24 | 25 | tests = [ 26 | "//Singleton/test:test", 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /Singleton/README.md: -------------------------------------------------------------------------------- 1 | `Singleton` 2 | ----------- 3 | 4 | Inspired by folly::Singleton this module contains generalized singleton class 5 | that allows classes to offer a singleton interface to users. The class 6 | ensures that singletons that are depenedant on each other are destroyed and 7 | created in such a way that all references are pointing to singleton instances 8 | that are valid. This implies that the singletons are automatically created 9 | and destroyed in a manner that would serialize their dependencies. 10 | 11 | ``` 12 | #include 13 | 14 | class TheSingleton { 15 | public: 16 | /** 17 | * declare the singleton class a friend so it can create an instance of 18 | * this class 19 | */ 20 | friend class Singleton; 21 | 22 | TheSingleton(const TheSingleton&) = delete; 23 | TheSingleton(TheSingleton&&) = delete; 24 | TheSingleton& operator=(const TheSingleton&) = delete; 25 | TheSingleton& operator=(TheSingleton&&) = delete; 26 | 27 | private: 28 | TheSingleton(); 29 | }; 30 | 31 | int main() { 32 | auto& singleton = sharp::Singleton::get(); 33 | // use singleton 34 | } 35 | ``` 36 | 37 | The C++ standard specifies that destruction of static objects will proceed in 38 | the reverse order of destruction, but there is no good way to serialize and 39 | generalize destruction of such objects at end time if there is no relation to 40 | the order of construction, this API offers such a possibility, by creating an 41 | instance of a proxy object like so 42 | `static Singleton::register_destruct_dependence dep;` You 43 | can register a destruction dependence for the singletons conveniently 44 | 45 | Double checked is used extensively throughout this module to enable fast 46 | asynchronous processing without any blocking. The only time the system blocks 47 | is when a singleton is being created and multiple things try and create the 48 | singleton at the same time. Also again at destruction a `std::shared_ptr` and 49 | a `std::weak_ptr` are used in sync to get fast on blocking access to a shared 50 | object. After destruction a thread can continue to hold on to a singleton as 51 | long as it holds a strong reference. Further the `std::weak_ptr::lock()` 52 | metohd being const implies thread safety and therefore no access blocks as 53 | long as the singleton is not being created. 54 | -------------------------------------------------------------------------------- /Singleton/Singleton.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace sharp { 4 | namespace detail { 5 | 6 | void SingletonStorage::destroy_singletons() { 7 | auto& singleton_storage = SingletonStorage::get(); 8 | 9 | // acquire the lock to the storage 10 | auto lock = singleton_storage.storage.lock(); 11 | 12 | // signal that singletons are being destroyed 13 | lock->are_singletons_being_destroyed = true; 14 | 15 | // loop through the objects in reverse order of creation and destroy 16 | // them 17 | for (; !lock->stack_creation.empty();) { 18 | auto wrapper_base_ptr = lock->control[lock->stack_creation.back()]; 19 | wrapper_base_ptr->destroy(); 20 | auto num_removed = lock->control.erase(lock->stack_creation.back()); 21 | assert(num_removed == 1); 22 | lock->stack_creation.pop_back(); 23 | } 24 | } 25 | 26 | SingletonStorage::SingletonStorage() { 27 | std::atexit(SingletonStorage::destroy_singletons); 28 | } 29 | 30 | } // namespace detail 31 | } // namespace sharp 32 | -------------------------------------------------------------------------------- /Singleton/Singleton.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @file Singleton.hpp 3 | * @author Aaryaman Sagar (rmn100@gmail.com) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | namespace sharp { 11 | 12 | template 13 | class Singleton { 14 | public: 15 | 16 | /** 17 | * The accessor function that can be used to create and use singleton 18 | * instances 19 | */ 20 | static std::shared_ptr get_strong(); 21 | }; 22 | 23 | } // namespace sharp 24 | 25 | #include 26 | -------------------------------------------------------------------------------- /Singleton/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Singleton:Singleton", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Singleton/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST(Singleton, simple_test_1) { 6 | auto integer_singleton_one = sharp::Singleton::get_strong(); 7 | auto integer_singleton_two = sharp::Singleton::get_strong(); 8 | EXPECT_EQ(integer_singleton_one.get(), integer_singleton_two.get()); 9 | } 10 | -------------------------------------------------------------------------------- /Tags/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Tags", 3 | header_namespace = "sharp/Tags", 4 | exported_headers = [ 5 | "Tags.hpp", 6 | ], 7 | visibility = [ 8 | "PUBLIC", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /Tags/README.md: -------------------------------------------------------------------------------- 1 | `Tags` 2 | ------ 3 | 4 | Contains simple tags that are used to be explicit in template code. 5 | -------------------------------------------------------------------------------- /Tags/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | ], 6 | deps = [ 7 | "//Tags:Tags", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /Tags/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | /* 9 | * Enum and overloaded functions to test correct tag dispatch 10 | */ 11 | enum class TagType {NORMAL, TYPE, INTEGRAL}; 12 | template 13 | TagType which_tag(typename Tag::tag_t) { 14 | return TagType::NORMAL; 15 | } 16 | template 17 | TagType which_tag(typename Tag::template tag_type_t) { 18 | return TagType::TYPE; 19 | } 20 | template 21 | TagType which_tag(typename Tag::template tag_integral_t) { 22 | return TagType::INTEGRAL; 23 | } 24 | 25 | TEST(TagsTest, TestGeneralizedTag) { 26 | /* 27 | * A test tag that is generated from a generalized tag 28 | */ 29 | class test_tag : public sharp::GeneralizedTag {}; 30 | 31 | EXPECT_EQ(which_tag(test_tag::tag), TagType::NORMAL); 32 | EXPECT_EQ(which_tag(test_tag::tag_type), TagType::TYPE); 33 | EXPECT_EQ(which_tag(test_tag::tag_integral<1>), 34 | TagType::INTEGRAL); 35 | } 36 | 37 | /** 38 | * Runs a test similar to the above for the tag type passed in as a template 39 | * parameter 40 | */ 41 | template 42 | static void test_which_tag_for_tag(); 43 | 44 | TEST(TagsTest, TestEachDefinedTag) { 45 | 46 | // run the test for each tag type 47 | test_which_tag_for_tag(); 48 | test_which_tag_for_tag(); 49 | test_which_tag_for_tag(); 50 | test_which_tag_for_tag(); 51 | } 52 | 53 | template 54 | static void test_which_tag_for_tag() { 55 | EXPECT_EQ(which_tag(Tag::tag), TagType::NORMAL); 56 | EXPECT_EQ(which_tag(Tag::template tag_type), TagType::TYPE); 57 | EXPECT_EQ(which_tag(Tag::template tag_integral<1>), TagType::INTEGRAL); 58 | } 59 | -------------------------------------------------------------------------------- /Threads/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Threads", 3 | header_namespace = "sharp/Threads", 4 | 5 | srcs = glob([ 6 | "**/*.cpp", 7 | ], excludes = [ 8 | "**/test/*.cpp", 9 | ]), 10 | exported_headers = subdir_glob([ 11 | ("", "**/*.hpp"), 12 | ("", "**/*.ipp"), 13 | ]), 14 | 15 | tests = [ 16 | "//Threads/test:test", 17 | ], 18 | 19 | visibility = [ 20 | "PUBLIC", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /Threads/README.md: -------------------------------------------------------------------------------- 1 | `Threads` 2 | --------- 3 | 4 | Common facilities for threads that are needed by other parts of the library 5 | 6 | Notable components are a utility to easily write concurrent test cases and a 7 | more generalized strictly superior version of `std::unique_lock` 8 | 9 | -------------------------------------------------------------------------------- /Threads/RecursiveMutex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace sharp { 9 | 10 | void RecursiveMutex::lock() { 11 | // acquire the mutex, increment the counter and then wait on the 12 | // condition_variable if this is not the thread that has the lock 13 | auto lck = std::unique_lock{this->mtx}; 14 | 15 | // dont block if this is the thread that has the lock, or if the value of 16 | // counter is 0 meaning that no thread is holding the lock 17 | if (this->is_lock_acquirable()) { 18 | this->acquire_lock(); 19 | return; 20 | } 21 | 22 | // else block on the condition_variable 23 | while (counter) { 24 | this->cv.wait(lck); 25 | } 26 | 27 | this->acquire_lock(); 28 | } 29 | 30 | void RecursiveMutex::unlock() { 31 | auto lck = std::unique_lock{this->mtx}; 32 | 33 | // check if the thread that releases the lock is the current thread, if it 34 | // is not then do not risk undefined POSIX behavior and throw an 35 | // exception, this is justified becasue the mutex already knows which 36 | // thread has the lock 37 | if (!counter) { 38 | throw std::runtime_error{"sharp::RecursiveMutex::unlock() called when " 39 | "the mutex is already unlocked"}; 40 | } 41 | if (std::this_thread::get_id() != this->thread_holding_lock) { 42 | throw std::runtime_error{"sharp::RecursiveMutex::unlock() called from " 43 | "a thread that does not hold the lock"}; 44 | } 45 | 46 | // decrement the counter and then notify on the condtion variable if the 47 | // counter has reached 0 48 | --counter; 49 | if (!counter) { 50 | this->cv.notify_one(); 51 | } 52 | } 53 | 54 | bool RecursiveMutex::try_lock() { 55 | auto lck = std::unique_lock{this->mtx}; 56 | 57 | // either acquire the lock if the current thread is holding the lock 58 | // already or if the lock is free 59 | if (this->is_lock_acquirable()) { 60 | this->acquire_lock(); 61 | return true; 62 | } 63 | 64 | return false; 65 | } 66 | 67 | bool RecursiveMutex::is_lock_acquirable() const { 68 | return this->thread_holding_lock == std::this_thread::get_id() || !counter; 69 | } 70 | 71 | void RecursiveMutex::acquire_lock() { 72 | ++counter; 73 | this->thread_holding_lock = std::this_thread::get_id(); 74 | } 75 | 76 | } // namespace sharp 77 | -------------------------------------------------------------------------------- /Threads/RecursiveMutex.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file RecursiveMutex.hpp 3 | * @author Aaryaman Sagar (rmn100@gmail.com) 4 | * 5 | * Contains a simple implementation of a recursive mutex implemented around 6 | * the C++ standard library monitors 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace sharp { 16 | 17 | class RecursiveMutex { 18 | public: 19 | /** 20 | * Acquire the lock held internally recursively, so if the same thread 21 | * that has the lock requests the lock, then this will not block 22 | */ 23 | void lock(); 24 | 25 | /** 26 | * release the lock 27 | */ 28 | void unlock(); 29 | 30 | /** 31 | * Non blocking try lock method that returns a true if we were able to 32 | * acquire the lock and false otherwise 33 | */ 34 | bool try_lock(); 35 | 36 | private: 37 | /** 38 | * Private functions. These are not gated with a mutex and should only be 39 | * called from within locked contexts 40 | */ 41 | void acquire_lock(); 42 | bool is_lock_acquirable() const; 43 | 44 | /** 45 | * private monitor 46 | */ 47 | std::condition_variable cv; 48 | std::mutex mtx; 49 | 50 | /** 51 | * The thread id for the thread holding the lock currently 52 | */ 53 | std::thread::id thread_holding_lock; 54 | 55 | /** 56 | * A counter to keep track of whether the lock has been released or not 57 | */ 58 | int counter{0}; 59 | }; 60 | 61 | } // namespace sharp 62 | -------------------------------------------------------------------------------- /Threads/ThreadTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace sharp { 7 | 8 | namespace { 9 | 10 | std::mutex mtx; 11 | std::condition_variable cv; 12 | int current_mark{0}; 13 | 14 | } // namespace anonymous 15 | 16 | namespace detail { 17 | 18 | ThreadTestRaii::ThreadTestRaii(int value_in) : value{value_in} { 19 | // wait for the current mark to be set by others 20 | auto lck = std::unique_lock{mtx}; 21 | while(current_mark != this->value) { 22 | cv.wait(lck); 23 | } 24 | } 25 | 26 | void ThreadTestRaii::release() { 27 | auto lck = std::unique_lock{mtx}; 28 | ++current_mark; 29 | cv.notify_all(); 30 | } 31 | 32 | ThreadTestRaii::~ThreadTestRaii() { 33 | if (this->should_release) { 34 | this->release(); 35 | } 36 | } 37 | 38 | ThreadTestRaii::ThreadTestRaii(ThreadTestRaii&& other) { 39 | this->value = other.value; 40 | this->should_release = other.should_release; 41 | other.should_release = false; 42 | } 43 | } 44 | 45 | detail::ThreadTestRaii ThreadTest::mark(int value) { 46 | return detail::ThreadTestRaii{value}; 47 | } 48 | 49 | void ThreadTest::reset() { 50 | std::lock_guard lck{mtx}; 51 | current_mark = 0; 52 | cv.notify_all(); 53 | } 54 | 55 | } // namespace sharp 56 | -------------------------------------------------------------------------------- /Threads/ThreadTest.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ThreadTest 3 | * @author Aaryaman Sagar 4 | * 5 | * Contains a simple threading abstraction that can be used to write 6 | * multithreaded test cases 7 | * 8 | * Write test cases like the following 9 | * 10 | * int main() { 11 | * auto th_one = std::thread{[]() { 12 | * auto mark = ThreadTest::mark(0); 13 | * // do something ... 14 | * }}; 15 | * 16 | * auto th_two = std::thread{[]() { 17 | * auto mark = ThreadTest::mark(2); 18 | * // do something else ... 19 | * }}; 20 | * 21 | * auto mark = ThreadTest::mark(1); 22 | * // something else... 23 | * mark.release(); 24 | * 25 | * th_one.join(); 26 | * th_two.join(); 27 | * } 28 | */ 29 | 30 | #pragma once 31 | 32 | namespace sharp { 33 | 34 | namespace detail { 35 | 36 | /** 37 | * Forward declaration of the RAII class that will be used to release a 38 | * mark and wait for a sequence of execution 39 | */ 40 | class ThreadTestRaii; 41 | 42 | } // namespace detail 43 | 44 | /** 45 | * @class ThreadTest 46 | * 47 | * A simple namespace like wrapper around two functions that are the essence 48 | * of this module, mark() and reset(), their uses and examples are documented 49 | * in the comment block right above their declarations 50 | */ 51 | class ThreadTest { 52 | public: 53 | 54 | /** 55 | * The basic function that is to be used by tests, this function blocks 56 | * until the mark has been satisfied 57 | * 58 | * For example you can easily set a sequence of execution for two threads 59 | * as in the following example 60 | * 61 | * int main() { 62 | * auto th_one = std::thread{[]() { 63 | * auto mark = ThreadTest::mark(0); 64 | * // do something ... 65 | * }}; 66 | * 67 | * auto th_two = std::thread{[]() { 68 | * auto mark = ThreadTest::mark(2); 69 | * // do something else ... 70 | * }}; 71 | * 72 | * ThreadTest::mark(1); 73 | * // something else... 74 | * 75 | * th_one.join(); 76 | * th_two.join(); 77 | * 78 | * This allows for somewhat less painful testing when it comes to making 79 | * multithreaded tests 80 | */ 81 | static detail::ThreadTestRaii mark(int value); 82 | 83 | /** 84 | * Resets the mark values back to 0 so that the first mark that will be 85 | * a future mark(0) will not block, and if a mark(0) is already waiting 86 | * this function will unblock that 87 | */ 88 | static void reset(); 89 | }; 90 | 91 | namespace detail { 92 | 93 | class ThreadTestRaii { 94 | public: 95 | 96 | /** 97 | * Destructor makes releases the current lock and notifies the next 98 | * mark to go 99 | */ 100 | ~ThreadTestRaii(); 101 | 102 | /** 103 | * Move constructor takes over the current marked scope 104 | */ 105 | ThreadTestRaii(ThreadTestRaii&&); 106 | 107 | /** 108 | * Releases the current block, use this function when you don't want 109 | * to wait for the destructor to make the next mark available to be 110 | * executed 111 | */ 112 | void release(); 113 | 114 | /** 115 | * Make friends with the ThreadTest class 116 | */ 117 | friend class sharp::ThreadTest; 118 | 119 | private: 120 | 121 | /** 122 | * The value that the current block holds 123 | */ 124 | int value; 125 | 126 | /** 127 | * Whether the destructor should release the current marked value or 128 | * not 129 | */ 130 | bool should_release{true}; 131 | 132 | /** 133 | * Private constructor only to be called from the mark() factory 134 | * function 135 | */ 136 | ThreadTestRaii(int value_in); 137 | }; 138 | 139 | } // namespace detail 140 | 141 | } // namespace sharp 142 | -------------------------------------------------------------------------------- /Threads/Threads.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @file Threads.hpp 3 | * @author Aaryaman Sagar 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | -------------------------------------------------------------------------------- /Threads/test/BUCK: -------------------------------------------------------------------------------- 1 | cxx_test( 2 | name = "test", 3 | srcs = [ 4 | "test.cpp", 5 | "UniqueLockTest.cpp", 6 | ], 7 | deps = [ 8 | "//Threads:Threads", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /Threads/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | auto mark_execution_sequence_point(int); 16 | 17 | class SequencePointImpl { 18 | public: 19 | friend auto mark_execution_sequence_point(int); 20 | 21 | ~SequencePointImpl() { 22 | auto lck = std::unique_lock{mtx}; 23 | ++current_sequence; 24 | cv.notify_all(); 25 | } 26 | 27 | private: 28 | SequencePointImpl(int point) : point_to_wait_on{point} { 29 | auto lck = std::unique_lock{mtx}; 30 | while (current_sequence != this->point_to_wait_on) { 31 | cv.wait(lck); 32 | } 33 | } 34 | 35 | static std::mutex mtx; 36 | static std::condition_variable cv; 37 | static int current_sequence; 38 | 39 | int point_to_wait_on; 40 | }; 41 | 42 | std::mutex SequencePointImpl::mtx; 43 | std::condition_variable SequencePointImpl::cv; 44 | int SequencePointImpl::current_sequence = 0; 45 | 46 | auto mark_execution_sequence_point(int point) { 47 | return SequencePointImpl{point}; 48 | } 49 | 50 | TEST(RecursiveMutex, simple_recursive_test_1) { 51 | sharp::RecursiveMutex recursive_mtx; 52 | auto th = std::thread{[&]() { 53 | recursive_mtx.lock(); 54 | recursive_mtx.lock(); 55 | }}; 56 | th.join(); 57 | } 58 | 59 | TEST(RecursiveMutex, simple_recursive_test_2) { 60 | sharp::RecursiveMutex recursive_mtx; 61 | 62 | auto th_one = std::thread{[&]() { 63 | { 64 | auto point = mark_execution_sequence_point(0); 65 | EXPECT_TRUE(recursive_mtx.try_lock()); 66 | EXPECT_TRUE(recursive_mtx.try_lock()); 67 | recursive_mtx.unlock(); 68 | } 69 | 70 | { 71 | auto point = mark_execution_sequence_point(2); 72 | recursive_mtx.unlock(); 73 | } 74 | }}; 75 | 76 | auto th_two = std::thread{[&]() { 77 | { 78 | auto point = mark_execution_sequence_point(1); 79 | EXPECT_FALSE(recursive_mtx.try_lock()); 80 | } 81 | 82 | { 83 | auto point = mark_execution_sequence_point(3); 84 | EXPECT_TRUE(recursive_mtx.try_lock()); 85 | } 86 | }}; 87 | 88 | th_one.join(); 89 | th_two.join(); 90 | } 91 | 92 | TEST(RecursiveMutex, exceptions_test_1) { 93 | sharp::RecursiveMutex recursive_mtx; 94 | 95 | // test whether unlocking the mutex multiple times, more than the limit 96 | // causes an exception to be thrown, it should be thrown 97 | try { 98 | recursive_mtx.lock(); 99 | recursive_mtx.unlock(); 100 | recursive_mtx.unlock(); 101 | EXPECT_TRUE(false); 102 | } catch (...) {} 103 | 104 | try { 105 | recursive_mtx.lock(); 106 | recursive_mtx.lock(); 107 | recursive_mtx.unlock(); 108 | recursive_mtx.unlock(); 109 | recursive_mtx.unlock(); 110 | EXPECT_TRUE(false); 111 | } catch (...) {} 112 | } 113 | 114 | TEST(RecursiveMutex, exceptions_test_2) { 115 | sharp::RecursiveMutex recursive_mtx; 116 | 117 | try { 118 | recursive_mtx.unlock(); 119 | EXPECT_TRUE(false); 120 | } catch (...) {} 121 | 122 | try { 123 | recursive_mtx.lock(); 124 | recursive_mtx.unlock(); 125 | recursive_mtx.unlock(); 126 | EXPECT_TRUE(false); 127 | } catch (...) {} 128 | } 129 | 130 | TEST(ThreadTest, simple_thread_test_test) { 131 | for (auto i = 0; i < 100; ++i) { 132 | 133 | auto str = std::string{}; 134 | 135 | sharp::ThreadTest::reset(); 136 | 137 | auto th_one = std::thread{[&]() { 138 | auto mark = sharp::ThreadTest::mark(1); 139 | str += "a"; 140 | }}; 141 | 142 | // sleep for 10 milliseconds to make sure that the above mark is hit, 143 | // otherwise would use the same testing suite to write this test, but 144 | // that would be a circular dependence, so stress test this to make 145 | // sure this is correct and all future tests using this that go by the 146 | // assumption that this is correct will not fault as long as this is 147 | // correct. This leads to a straight correctness dependence which is 148 | // as strong as the correctness of this test itself, which leads to a 149 | // much better testing correctness result. 150 | // 151 | // Test the small things the bad way and test the big things the good 152 | // way 153 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 154 | 155 | auto mark = sharp::ThreadTest::mark(0); 156 | str += "b"; 157 | mark.release(); 158 | 159 | th_one.join(); 160 | EXPECT_EQ(str, "ba"); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Traits/BUCK: -------------------------------------------------------------------------------- 1 | cxx_library( 2 | name = "Traits", 3 | header_namespace = "sharp/Traits", 4 | exported_headers = [ 5 | "Traits.hpp", 6 | "detail/IsInstantiationOf.hpp", 7 | "detail/IsCallable.hpp", 8 | "detail/IsOneOf.hpp", 9 | "detail/TypeLists.hpp", 10 | "detail/FunctionIntrospect.hpp", 11 | "detail/UnwrapPair.hpp", 12 | "detail/Algorithm.hpp", 13 | "detail/Functional.hpp", 14 | "detail/VoidT.hpp", 15 | "detail/Utility.hpp", 16 | ], 17 | deps = [ 18 | "//Tags:Tags", 19 | ], 20 | visibility = [ 21 | "PUBLIC", 22 | ], 23 | 24 | tests = [ 25 | "//Traits/test:test", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /Traits/Traits.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @file Traits.hpp 3 | * @author Aaryaman Sagar (rmn100@gmail.com) 4 | * 5 | * This file contains some template metaprogramming interfaces that I 6 | * occasionally find useful in addition to the C++ standard library header 7 | * 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | -------------------------------------------------------------------------------- /Traits/detail/Conjunction.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Conjunction.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * An implementation of conjunction until fold expressions and c++17 6 | * std::conjunction are supported 7 | */ 8 | 9 | #pragma once 10 | 11 | namespace sharp { 12 | 13 | /** 14 | * TODO deprecate this 15 | */ 16 | template 17 | struct conjunction : std::true_type { }; 18 | template 19 | struct conjunction : B1 { }; 20 | template 21 | struct conjunction 22 | : std::conditional_t, B1> {}; 23 | 24 | template 25 | inline constexpr bool conjunction_v = conjunction::value; 26 | 27 | } // namespace sharp 28 | -------------------------------------------------------------------------------- /Traits/detail/Functional.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Functional.hpp 3 | * @author Aaryaman Sagar 4 | * 5 | * This file contains meta functions that can be used to transform type based 6 | * traits into another type based traits by binding a few arguments to the 7 | * type just as std::bind does with callables 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | namespace sharp { 15 | 16 | /** 17 | * @class Bind 18 | * 19 | * Binds the trait metafunction to the arguments as provided by the type list 20 | */ 21 | template