├── README.md ├── better_member_func.cpp ├── example.cpp ├── member_func.cpp └── practical.cpp /README.md: -------------------------------------------------------------------------------- 1 | # C++ Python-like Decorators 2 | How to write decorator functions in modern C++14 or higher 3 | 4 | Works across MSVC, GNU CC, and Clang compilers 5 | 6 | ## Skip the tutorial and view the final results 7 | 8 | [tutorial demo](https://godbolt.org/z/nV7gjP) 9 | 10 | [practical demo](https://godbolt.org/z/o73nZh) 11 | 12 | [compile-time decorator demo](https://godbolt.org/z/gCQk3S) 13 | 14 | [run-time member function demo](https://godbolt.org/z/Fy-9XT) 15 | 16 | [reusable member function demo](https://godbolt.org/z/w4P9V-) 17 | 18 | # The goal 19 | Python has a nice feature that allows function definitions to be wrapped by other existing functions. "Wrapping" consists of taking a function in as an argument and returning a new aggregated function composed of the input function and the wrapper function. The wrapper functions themselves are called 'decorator' functions because they decorate, or otherwise extend, the original input function's behavior. 20 | 21 | The syntax for this in python begin with `@` immediately followed by the decorator function name. On the next line, a brand new function definition begins. The result is that the new function will be automatically decorated by the function we declared with the `@` symbol. A python decorator would look like this: 22 | 23 | ```python 24 | def stars(func): 25 | def inner(*args, **kwargs): 26 | print("**********") 27 | func(*args, **kwargs) 28 | print("**********") 29 | 30 | return inner 31 | 32 | # decorator syntax 33 | @stars 34 | def hello_world(): 35 | print("hello world!") 36 | 37 | # The following prints: 38 | # 39 | # ********** 40 | # hello world! 41 | # ********** 42 | hello_world() 43 | ``` 44 | 45 | The decorating function `stars(...)` will take in any kind of input and pass it along to its inner `func` object, whatever that may be. In this case, `hello_world()` does not take in any arguments so `func()` will simply be called. 46 | 47 | The `@stars` syntax is sugar for `hello_world = stars(hello_world)`. 48 | 49 | This is a really nice feature for python that, as a C++ enthusiast, I would like to use in my own projects. In this tutorial I'm going to make a close equivalent without using magic macros or meta-object compilation tools. 50 | 51 | # Accepting any arbitrary functor in modern C++ 52 | Python decorator functions take in a _function_ as its argument. I toyed with a variety of concepts and discovered quickly that lambdas are not as versatile as I had hoped they would be. Consider the following code: 53 | 54 | [goto godbolt](https://godbolt.org/z/4R7Elv) 55 | 56 | ```cpp 57 | template 58 | auto stars(R(*in)(Args...)) { 59 | return [in](Args&&... args) { 60 | std::cout << "*******" << std::endl; 61 | in(); 62 | std::cout << "*******" << std::endl; 63 | }; 64 | } 65 | 66 | void hello() { 67 | cout << "hello, world!" << endl; 68 | } 69 | 70 | template 71 | auto smart_divide(R(*in)(Args...)) { 72 | return [in](float a, float b) { 73 | std::cout << "I am going to divide a and b" << std::endl; 74 | 75 | if(b == 0) { 76 | std::cout << "Whoops! cannot divide" << std::endl; 77 | return 0.0f; 78 | } 79 | 80 | return in(a, b); 81 | }; 82 | } 83 | 84 | float divide(float a, float b) { 85 | return a/b; 86 | } 87 | ``` 88 | 89 | This tries to achieve the following python code: 90 | 91 | ```python 92 | def smart_divide(func): 93 | def inner(a,b): 94 | print("I am going to divide",a,"and",b) 95 | if b == 0: 96 | print("Whoops! cannot divide") 97 | return 98 | 99 | return func(a,b) 100 | return inner 101 | 102 | @smart_divide 103 | def divide(a,b): 104 | return a/b 105 | ``` 106 | 107 | It works! Great! But try uncommenting line 66. It does not compile. If you looks closely at the compiler output it has trouble deducing the function pointer types from the lambda object returned by the inner-most decorator. This is because **lambdas with capture cannot be converted to function pointers** 108 | 109 | If we were to introduce a struct to hold our arbitrary functors, we'd fall into the same problem. By limiting ourselves to a specific expected function-pointer syntax, we lose the ability to accept just about any type we want. The solution is to use a single `template` before our decorators to let the compiler know the decorator can take in just about anything we throw at it. 110 | 111 | Now we can nest multiple decorator functions together! ... not so fast... 112 | 113 | Now we've lost the type information from `Args...` in our function signature. Luckily there is something we can do about this in C++14 and onward... 114 | 115 | # Returning a closure that can accept any number of args 116 | We need to build a function, in our function, that can also accept an arbitrary set of inputs and pass those along to our captured input function. To reiterate, our _returned function_ needs to be able to _forward all the arguments_ to the function we are trying to decorate. 117 | 118 | Python gets around this problem by using special arguments `*args` and `**kwargs`. I won't go into detail what these two differerent notations mean, but for our problem task they are equivalent to C++ variadic arguments. They can be written like so: 119 | 120 | ```cpp 121 | template 122 | void foo(Args&&... args) { 123 | bar(std::forward(args)...); 124 | } 125 | ``` 126 | 127 | Anything passed into the function are forwarded as arguments for the inner function `bar`. If the types match, the compiler will accept the input. This is what we want, but remember we're returning a function inside another function. Prior to C++14 this might have been impossible to achieve nicely. Thankfully C++14 introduced **template lambdas** 128 | 129 | ```cpp 130 | return [func](Args&&... args) { 131 | std::cout << "*******" << std::endl; 132 | func(std::forward(args)...); // forward all arguments 133 | std::cout << "\n*******" << std::endl; 134 | }; 135 | ``` 136 | 137 | # Clang and MSVC - `auto` pack for the win 138 | Try toggling the compiler options in godbolt from GNU C Compiler 9.1 to latest Clang or MSVC. No matter what standard you specify, it won't compile. We were so close! Let's inspect further. Some google searches and forum scrolling later, it seems the trusty GCC might be ahead of the curve with generic lambdas in C++14. To quote one forum user: 139 | 140 | > C++20 will come with templated and conceptualized lambdas. The feature has already been integrated into the standard draft. 141 | 142 | I was stuck on this for quite some time until I discovered this neat trick by trial and error: 143 | 144 | ```cpp 145 | template 146 | auto output(const F& func) { 147 | return [func](auto&&... args) { 148 | std::cout << func(std::forward(args)...); 149 | }; 150 | } 151 | ``` 152 | 153 | By specifying the arguments for the closure as an `auto` pack, we can avoid template type parameters all together - much more readable! 154 | 155 | # Nested decorators 156 | Great, we can begin putting it all together! We have: 157 | 158 | * Function that returns function (by lambda closures) (CHECK) 159 | * "Decorator function" that can accept any input function using a template typename (CHECK) 160 | * Inner function can also accept arbitrary args passed from outer function (using auto packs) (CHECK) 161 | 162 | Now we can check to see if we can further nest the decorators... 163 | 164 | [goto godbolt](https://godbolt.org/z/NW6jWE) 165 | 166 | ```cpp 167 | // line 57 -- four decorator functions! 168 | auto d = stars(output(smart_divide(divide))); 169 | d(12.0f, 3.0f); 170 | ``` 171 | 172 | output is 173 | 174 | ```cpp 175 | ******* 176 | I am going to divide a=12 and b=3 177 | 4 178 | ******* 179 | ``` 180 | 181 | First the `stars` decorator is called printing `**********` to our topmost row. 182 | Then the `output` function prints the result of the next function to `cout` so we can see it. The result of the next function is covered by the next two nested functions. 183 | 184 | The `smart_divide` function checks the input passed in from the top of the chain `12.0f, 3.0f` if we are dividing by zero or not before forwarding args to the next function `divide` which calculates the result. `divide` returns the result and `smart_divide` returns that result to `output`. 185 | 186 | Finally `stars` scope is about to end and prints the last `*********` row 187 | 188 | # Works out of the box as-is 189 | Check out line 51 using `printf` 190 | 191 | ```cpp 192 | auto p = stars(printf); 193 | p("C++ is %s!", "epic"); 194 | ``` 195 | 196 | output is 197 | 198 | ```cpp 199 | ******* 200 | C++ is epic! 201 | ******* 202 | ``` 203 | 204 | I think I found my new favorite C++ concept for outputting log files, don't you feel the same way? :) 205 | 206 | # Practical examples 207 | There's a lot of debate about C++'s exception handling, lack thereof, and controversial best practices. We can solve a lot of headache by providing decorator functions to let throwable functions fail without fear. 208 | 209 | Consider this example: 210 | [goto godbolt](https://godbolt.org/z/VV2rRh) 211 | 212 | We can let the function silently fail and we can choose to supply another decorator function to pipe the output to a log file. Alternatively we could also check the return value of the exception (using better value types of course this is just an example) to determine whether to shutdown the application or not. 213 | 214 | ```cpp 215 | auto read_safe = exception_fail_safe(file_read); 216 | 217 | // assume read_safe() returns some optional_type<> struct 218 | if(!read_safe("missing_file.txt", buff, &sz).OK) { 219 | // Whoops! We needed this file. Quit immediately! 220 | app.abort(); 221 | return; 222 | } 223 | ``` 224 | 225 | # Decorating functions at compile-time! 226 | After this tutorial was released a user by the online name [robin-m](http://robinmoussu.gitlab.io/blog) pointed out that the functions _could_ be decorated at compile-time as opposed to runtime (as I previously acknowledged this seemed to be the only way in C++ without macro magic). Robin-m suggests using `constexpr` in the function declaration. 227 | 228 | [goto godbolt](https://godbolt.org/z/gCQk3S) 229 | 230 | ```cpp 231 | ///////////////////////////////////////// 232 | // final decorated functions // 233 | ///////////////////////////////////////// 234 | 235 | constexpr auto hello = stars(hello_impl); 236 | constexpr auto divide = stars(output(smart_divide(divide_impl))); 237 | constexpr auto print = stars(printf); 238 | 239 | int main() { 240 | // ... 241 | } 242 | ``` 243 | 244 | This allows us to separate the regular function implementation we may wish to decorate from the final decorated function we'll use in our programs. This means any modification to these 'final' functions will happen globally across our code base and not limited to a single routine's scope. This increases reusability, modularity, and readability - no sense repeating yourself twice! 245 | 246 | # Further Applications: Decorating member functions 247 | We can decorate member functions in C++. To be clear, we cannot change the existing member function itself, but we can bind a reference to the member function and call it. 248 | 249 | Let's take an example that uses everything we learned so far. We want to produce a grocery checkout program to tell us the cost of each bag of apples we picked. We want to throw exceptions when invalid arguments are supplied but we want it to do so safely, log a nice timestamp somewhere, and display the price if valid. 250 | 251 | [goto godbolt](https://godbolt.org/z/3fS4rG) 252 | 253 | ```cpp 254 | // exception decorator for optional return types 255 | template 256 | auto exception_fail_safe(const F& func) { 257 | return [func](auto&&... args) 258 | -> optional_type(args)...))> { 259 | using R = optional_type(args)...))>; 260 | 261 | try { 262 | return R(func(std::forward(args)...)); 263 | } catch(std::iostream::failure& e) { 264 | return R(false, e.what()); 265 | } catch(std::exception& e) { 266 | return R(false, e.what()); 267 | } catch(...) { 268 | // This ... catch clause will capture any exception thrown 269 | return R(false, std::string("Exception caught: default exception")); 270 | } 271 | }; 272 | } 273 | ``` 274 | 275 | This decorator returns an `optional_type` which for our purposes is very crude but allows us to check if the return value of the function was OK or if an exception was thrown. If it was, we want to see what it is. We declare the lambda to share the same return value as the closure with `-> optional_type(args)...))>`. We use the same try-catch as before but supply different constructors for our `optional_type`. 276 | 277 | We now want to use this decorator on our `double apple::calculate_cost(int, double)` member function. We cannot change what exists, but we can turn it into a functor using `std::bind`. 278 | 279 | ```cpp 280 | apples groceries(1.09); 281 | auto get_cost = exception_fail_safe(std::bind(&apples::calculate_cost, &groceries, _1, _2)); 282 | ``` 283 | 284 | We create a vector of 4 results. 2 of them will throw errors while the other 2 will run just fine. Let's see our results. 285 | 286 | ```cpp 287 | [1] There was an error: apples must weigh more than 0 ounces 288 | 289 | [2] Bag cost $2.398 290 | 291 | [3] Bag cost $7.085 292 | 293 | [4] There was an error: must have 1 or more apples 294 | ``` 295 | 296 | # Reusable member-function decorators 297 | The last example was not reusable. It was bound to exactly one object. Let's refactor that and decorate our output a little more. 298 | 299 | ```cpp 300 | template 301 | auto visit_apples(const F& func) { 302 | return [func](apples& a, auto&&... args) { 303 | return (a.*func)(std::forward(args)...); 304 | }; 305 | } 306 | ``` 307 | 308 | All we need is a function at the very deepest part of our nest that takes in the object by reference and its member function. 309 | 310 | We could wrap it like so 311 | 312 | ```cpp 313 | apples groceries(2.45); 314 | auto get_cost = exception_fail_safe(visit_apples(&apples::calculate_cost)); 315 | get_cost(groceries, 10, 3); // lots of big apples! 316 | ``` 317 | 318 | Crude but proves a point. We've basically reinvented a visitor pattern. Our decorator functions visit an object and invoke the member function on our behalf since we cannot modify the class definition. The rest is the same as it was before: functions nested in functions like little russian dolls. 319 | 320 | We can completely take advantage of this functional-like syntax and have all our output decorators return the result value as well. 321 | 322 | [goto godbolt](https://godbolt.org/z/w4P9V-) 323 | 324 | ```cpp 325 | // Different prices for different apples 326 | apples groceries1(1.09), groceries2(3.0), groceries3(4.0); 327 | auto get_cost = log_time(output(exception_fail_safe(visit_apples(&apples::calculate_cost)))); 328 | 329 | auto vec = { 330 | get_cost(groceries2, 2, 1.1), 331 | get_cost(groceries3, 5, 1.3), 332 | get_cost(groceries1, 4, 0) 333 | }; 334 | ``` 335 | 336 | Which outputs 337 | 338 | ```cpp 339 | Bag cost $6.6 340 | 341 | > Logged at Mon Aug 5 02:17:10 2019 342 | 343 | 344 | Bag cost $26 345 | 346 | > Logged at Mon Aug 5 02:17:10 2019 347 | 348 | 349 | There was an error: apples must weigh more than 0 ounces 350 | 351 | > Logged at Mon Aug 5 02:17:10 2019 352 | ``` 353 | 354 | # Writing Python's @classmethod 355 | In python, we have a similar decorator to properly decorate member functions: `@classmethod`. This decorator specifically tells the interpreter to pass `self` into the decorator chain, if used, so that the member function can be called correctly- specifically in the event of inherited member functions. [Further reading on stackoverflow](https://stackoverflow.com/questions/3782040/python-decorators-that-are-part-of-a-base-class-cannot-be-used-to-decorate-membe) 356 | 357 | We needed to pass the instance of the object into the decorator chain and with a quick re-write we can make this class visitor decorate function universal. 358 | 359 | Simply swap out `apples&` for `auto&`: 360 | 361 | [goto godbolt](https://godbolt.org/z/nQpdfN) 362 | 363 | ```cpp 364 | //////////////////////////////////// 365 | // visitor function // 366 | //////////////////////////////////// 367 | 368 | template 369 | constexpr auto classmethod(F func) { 370 | return [func](auto& a, auto&&... args) { 371 | return (a.*func)(args...); 372 | }; 373 | } 374 | ``` 375 | 376 | Now we can visit any class type member function. 377 | 378 | # After-thoughts 379 | Unlike python, C++ doesn't let us redefine functions on the fly, but we could get closer to python syntax if we had some kind of intermediary functor type that we could reassign e.g. 380 | 381 | ```cpp 382 | decorated_functor d = smart_divide(divide); 383 | 384 | // reassignment 385 | d = stars(output(d)); 386 | ``` 387 | 388 | Unlike python, assignment of arbitrary types in C++ is almost next to impossible without type erasure. 389 | We solved this by using templates but every new nested function returns a new type that the compiler sees. 390 | And as we discovered at the beginning, lambdas with capture do not dissolve into C++ pointers as we might expect either. 391 | 392 | With all the complexities at hand, this task non-trivial. 393 | 394 | _update!_ 395 | I tackled this design [here](https://github.com/TheMaverickProgrammer/C-Python-Like-Class-Member-Decorators) making it possible for classes to have re-assignable member function types. 396 | 397 | This challenge took about 2 days plugged in and was a lot of fun. I learned a lot on the way and discovered something pretty useful. Thanks for reading! 398 | -------------------------------------------------------------------------------- /better_member_func.cpp: -------------------------------------------------------------------------------- 1 | // practical example of modern C++ decorators 2 | // view the full tutorial at https://github.com/TheMaverickProgrammer/C-Python-like-Decorators 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std::placeholders; 16 | using namespace std; 17 | 18 | //////////////////////////////////// 19 | // weak optional value structure // 20 | //////////////////////////////////// 21 | template 22 | struct optional_type { 23 | T value; 24 | bool OK; 25 | bool BAD; 26 | std::string msg; 27 | 28 | optional_type(T&& t) : value(std::move(t)) { OK = true; BAD = false; } 29 | optional_type(bool ok, std::string msg="") : msg(std::move(msg)) { OK = ok; BAD = !ok; } 30 | }; 31 | 32 | ///////////////////////////////////// 33 | // decorators // 34 | ///////////////////////////////////// 35 | 36 | // exception decorator for optional return types 37 | template 38 | auto exception_fail_safe(const F& func) { 39 | return [func](auto&&... args) 40 | -> optional_type(args)...))> { 41 | using R = optional_type(args)...))>; 42 | 43 | try { 44 | return R(func(std::forward(args)...)); 45 | } catch(std::iostream::failure& e) { 46 | return R(false, e.what()); 47 | } catch(std::exception& e) { 48 | return R(false, e.what()); 49 | } catch(...) { 50 | // This ... catch clause will capture any exception thrown 51 | return R(false, std::string("Exception caught: default exception")); 52 | } 53 | }; 54 | } 55 | 56 | // this decorator can output our optional data 57 | template 58 | auto output(const F& func) { 59 | return [func](auto&&... args) { 60 | auto opt = func(std::forward(args)...); 61 | 62 | if(opt.BAD) { 63 | std::cout << "There was an error: " << opt.msg << std::endl; 64 | } else { 65 | std::cout << "Bag cost $" << opt.value << std::endl; 66 | } 67 | 68 | return opt; 69 | }; 70 | } 71 | 72 | // this decorator prints time and returns value of inner function 73 | // returning is purely conditional based on our needs, in this case 74 | // we want to take advantage of the functional-like syntax we've created 75 | template 76 | auto log_time(const F& func) { 77 | return [func](auto&&... args) { 78 | auto now = std::chrono::system_clock::now(); 79 | std::time_t time = std::chrono::system_clock::to_time_t(now); 80 | auto opt = func(std::forward(args)...); 81 | std::cout << "> Logged at " << std::ctime(&time) << std::endl; 82 | 83 | return opt; 84 | }; 85 | } 86 | 87 | /////////////////////////////////////////////// 88 | // an example class with a member function // 89 | /////////////////////////////////////////////// 90 | struct apples { 91 | // ctor 92 | apples(double cost_per_apple) : cost_per_apple(cost_per_apple) { } 93 | 94 | // member function that throws 95 | double calculate_cost(int count, double weight) { 96 | if(count <= 0) 97 | throw std::runtime_error("must have 1 or more apples"); 98 | 99 | if(weight <= 0) 100 | throw std::runtime_error("apples must weigh more than 0 ounces"); 101 | 102 | return count*weight*cost_per_apple; 103 | } 104 | 105 | double cost_per_apple; 106 | }; 107 | 108 | //////////////////////////////////// 109 | // visitor function // 110 | //////////////////////////////////// 111 | 112 | template 113 | auto visit_apples(const F& func) { 114 | return [func](apples& a, auto&&... args) { 115 | return (a.*func)(std::forward(args)...); 116 | }; 117 | } 118 | 119 | //////////////////////////////////// 120 | // final decorated function // 121 | //////////////////////////////////// 122 | 123 | auto get_cost = log_time(output(exception_fail_safe(visit_apples(&apples::calculate_cost)))); 124 | 125 | int main() { 126 | // Different prices for different apples 127 | apples groceries1(1.09), groceries2(3.0), groceries3(4.0); 128 | 129 | // this vector will contain optional values 130 | // at construction, it will also print what we want to see 131 | auto vec = { 132 | get_cost(groceries2, 2, 1.1), 133 | get_cost(groceries3, 5, 1.3), 134 | get_cost(groceries1, 4, 0) 135 | }; 136 | 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | /* the goal was to make reusable functions that behave like python decorator functions 2 | * e.g. decorators wrap existing functions and return aggregate function 3 | * auto foo = exception_fail_safe(fileread("missing_file.txt")); 4 | * foo(); // works! 5 | * 6 | * auto bar = output_result(foo); // further decoration 7 | * bar(); // output: "Exception caught: missing_file.txt not found!" 8 | * 9 | * Play with source at https://godbolt.org/z/jflOuu 10 | * View the tutorial at https://github.com/TheMaverickProgrammer/C-Python-like-Decorators 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | using namespace std; 17 | 18 | ///////////////////////// 19 | // decorators // 20 | ///////////////////////// 21 | 22 | template 23 | constexpr auto stars(const F& func) { 24 | return [func](auto&&... args) { 25 | cout << "*******" << endl; 26 | func(forward(args)...); 27 | cout << "\n*******" << endl; 28 | }; 29 | } 30 | 31 | template 32 | constexpr auto smart_divide(const F& func) { 33 | return [func](float a, float b) { 34 | cout << "I am going to divide a=" << a << " and b=" << b << endl; 35 | 36 | if(b == 0) { 37 | cout << "Whoops! cannot divide" << endl; 38 | return 0.0f; 39 | } 40 | 41 | return func(a, b); 42 | }; 43 | } 44 | 45 | template 46 | constexpr auto output(const F& func) { 47 | return [func](auto&&... args) { 48 | cout << func(forward(args)...); 49 | }; 50 | } 51 | 52 | //////////////////////////////////////// 53 | // function implementations // 54 | //////////////////////////////////////// 55 | 56 | void hello_impl() { 57 | cout << "hello, world!"; 58 | } 59 | 60 | 61 | float divide_impl(float a, float b) { 62 | return a/b; 63 | } 64 | 65 | ///////////////////////////////////////// 66 | // final decorated functions // 67 | ///////////////////////////////////////// 68 | 69 | constexpr auto hello = stars(hello_impl); 70 | constexpr auto divide = stars(output(smart_divide(divide_impl))); 71 | constexpr auto print = stars(printf); 72 | 73 | // example for declaring a decorated function in one step. 74 | // foo() cannot be templated for now, but C++20 should make this possible 75 | constexpr auto foo = stars( 76 | [](unsigned n=10) { 77 | for (unsigned i = 0; i < n; ++i) 78 | cout << "FOO!\n"; 79 | } 80 | ); 81 | 82 | int main() { 83 | 84 | hello(); 85 | 86 | divide(12.0f, 3.0f); 87 | 88 | print("C++ is %s!", "epic!"); 89 | 90 | foo(3); 91 | 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /member_func.cpp: -------------------------------------------------------------------------------- 1 | // practical example of modern C++ decorators 2 | // view the full tutorial at https://github.com/TheMaverickProgrammer/C-Python-like-Decorators 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std::placeholders; 16 | using namespace std; 17 | 18 | /////////////////////////////////// 19 | // weak optional value structure // 20 | /////////////////////////////////// 21 | template 22 | struct optional_type { 23 | T value; 24 | bool OK; 25 | bool BAD; 26 | std::string msg; 27 | 28 | optional_type(T&& t) : value(std::move(t)) { OK = true; BAD = false; } 29 | optional_type(bool ok, std::string msg="") : msg(std::move(msg)) { OK = ok; BAD = !ok; } 30 | }; 31 | 32 | //////////////////////////////////// 33 | // decorators // 34 | //////////////////////////////////// 35 | 36 | // exception decorator for optional return types 37 | template 38 | auto exception_fail_safe(const F& func) { 39 | return [func](auto&&... args) 40 | -> optional_type(args)...))> { 41 | using R = optional_type(args)...))>; 42 | 43 | try { 44 | return R(func(std::forward(args)...)); 45 | } catch(std::iostream::failure& e) { 46 | return R(false, e.what()); 47 | } catch(std::exception& e) { 48 | return R(false, e.what()); 49 | } catch(...) { 50 | // This ... catch clause will capture any exception thrown 51 | return R(false, std::string("Exception caught: default exception")); 52 | } 53 | }; 54 | } 55 | 56 | template 57 | auto output(const F& func) { 58 | return [func](auto&&... args) { 59 | std::cout << func(std::forward(args)...) << std::endl; 60 | }; 61 | } 62 | 63 | template 64 | auto log_time(const F& func) { 65 | return [func](auto&&... args) { 66 | auto now = std::chrono::system_clock::now(); 67 | std::time_t time = std::chrono::system_clock::to_time_t(now); 68 | func(std::forward(args)...); 69 | std::cout << "> Logged at " << std::ctime(&time) << std::endl; 70 | }; 71 | } 72 | 73 | ///////////////////////////////////////////// 74 | // an example class with a member function // 75 | ///////////////////////////////////////////// 76 | 77 | struct apples { 78 | apples(double cost_per_apple) : cost_per_apple(cost_per_apple) { } 79 | 80 | double calculate_cost(int count, double weight) { 81 | if(count <= 0) 82 | throw std::runtime_error("must have 1 or more apples"); 83 | 84 | if(weight <= 0) 85 | throw std::runtime_error("apples must weigh more than 0 ounces"); 86 | 87 | return count*weight*cost_per_apple; 88 | } 89 | 90 | double cost_per_apple; 91 | }; 92 | 93 | int main() { 94 | // $1.09 per apple 95 | apples groceries(1.09); 96 | 97 | // we must bind the object and member function in scope 98 | auto get_cost = exception_fail_safe(std::bind(&apples::calculate_cost, &groceries, _1, _2)); 99 | 100 | // create a vector of optional result values 101 | auto vec = { get_cost(4, 0), get_cost(2, 1.1), get_cost(5, 1.3), get_cost(0, 2.45) }; 102 | 103 | // step through the vector and print values 104 | int idx = 0; 105 | for(auto& opt : vec) { 106 | std::cout << "[" << ++idx << "] "; 107 | 108 | if(opt.BAD) { 109 | std::cout << "There was an error: " << opt.msg << std::endl; 110 | } else { 111 | std::cout << "Bag cost $" << opt.value << std::endl; 112 | } 113 | } 114 | 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /practical.cpp: -------------------------------------------------------------------------------- 1 | // practical example of modern C++ decorators 2 | // view the full tutorial at https://github.com/TheMaverickProgrammer/C-Python-like-Decorators 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | ///////////////////////// 13 | // decorators // 14 | ///////////////////////// 15 | 16 | // our basic fail-safe exception decorator will return strings for output in this demo 17 | // see https://github.com/TheMaverickProgrammer/C-Python-like-Decorators/blob/master/README.md#further-applications-decorating-member-functions 18 | // for a more robust example 19 | template 20 | auto exception_fail_safe(const F& func) { 21 | return [func](auto&&... args) { 22 | try { 23 | func(std::forward(args)...); 24 | } catch(std::iostream::failure& e) { 25 | return std::string("Exception caught: ") + e.what(); 26 | } catch(...) { 27 | // This ... catch clause will capture any exception thrown 28 | return std::string("Exception caught: default exception"); 29 | } 30 | 31 | return std::string("OK"); // No exceptions! 32 | }; 33 | } 34 | 35 | template 36 | auto output(const F& func) { 37 | return [func](auto&&... args) { 38 | std::cout << func(std::forward(args)...) << std::endl; 39 | }; 40 | } 41 | 42 | template 43 | auto log_time(const F& func) { 44 | return [func](auto&&... args) { 45 | auto now = std::chrono::system_clock::now(); 46 | std::time_t time = std::chrono::system_clock::to_time_t(now); 47 | func(std::forward(args)...); 48 | std::cout << "> Logged at " << std::ctime(&time) << std::endl; 49 | }; 50 | } 51 | 52 | ////////////////////////////// 53 | // function implementations // 54 | ////////////////////////////// 55 | 56 | void file_read_impl(const char* path, char* data, int* sz) { 57 | // for demo purposes, always fail 58 | std::string msg = std::string(path) + std::string(" not found!"); 59 | throw std::iostream::failure(msg.c_str()); 60 | } 61 | 62 | /////////////////////////////// 63 | // final decorated functions // 64 | /////////////////////////////// 65 | 66 | auto file_read = exception_fail_safe(file_read_impl); 67 | auto print_file_read = log_time(output(file_read)); 68 | 69 | int main() { 70 | // some demo dummy data for our mock file read... 71 | char* buff = 0; 72 | int sz = 0; 73 | 74 | std::cout << "First read fails silently" << std::endl; 75 | file_read("missing_file.txt", buff, &sz); 76 | 77 | std::cout << "\nSecond read fails and prints to the console with time:" << std::endl; 78 | print_file_read("missing_file.txt", buff, &sz); 79 | 80 | return 0; 81 | } 82 | --------------------------------------------------------------------------------