C++11 functional decomposition - easy way to do AOP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is example from | |
// http://vitiy.info/c11-functional-decomposition-easy-way-to-do-aop/ | |
// by Victor Laskin | |
#include <iostream> | |
#include <functional> | |
#include <map> | |
#include <vector> | |
#include <memory> | |
#include <chrono> | |
#include <mutex> | |
using namespace std; | |
#define LOG std::cout | |
#define NL std::endl | |
/// Simple immutable data | |
class UserData { | |
public: | |
const int id; | |
const string name; | |
const int parent; | |
UserData(int id, string name, int parent) : id(id), name(name), parent(parent) {} | |
}; | |
/// Shared pointer to immutable data | |
using User = std::shared_ptr<UserData>; | |
/// Error type - code + description | |
class Error { | |
public: | |
Error(int code, string message) : code(code), message(message) {} | |
Error(const Error& e) : code(e.code), message(e.message) {} | |
const int code; | |
const string message; | |
}; | |
/// MayBe monad from Haskel over shared_ptr | |
/// with additional error field | |
template < typename T > | |
class Maybe { | |
private: | |
const T data; | |
const shared_ptr<Error> error; | |
public: | |
Maybe(T data) : data(std::forward<T>(data)), error(nullptr) {} | |
Maybe() : data(nullptr), error(nullptr) {} | |
Maybe(decltype(nullptr) nothing) : data(nullptr), error(nullptr) {} | |
Maybe(Error&& error) : data(nullptr), error(make_shared<Error>(error)) {} | |
bool isEmpty() { return (data == nullptr); }; | |
bool hasError() { return (error != nullptr); }; | |
T operator()(){ return data; }; | |
shared_ptr<Error> getError(){ return error; }; | |
}; | |
template <class T> | |
Maybe<T> just(T t) | |
{ | |
return Maybe<T>(t); | |
} | |
// Helpers to convert lambda into std::function | |
template <typename Function> | |
struct function_traits | |
: public function_traits<decltype(&Function::operator())> | |
{}; | |
template <typename ClassType, typename ReturnType, typename... Args> | |
struct function_traits<ReturnType(ClassType::*)(Args...) const> | |
{ | |
typedef ReturnType (*pointer)(Args...); | |
typedef std::function<ReturnType(Args...)> function; | |
}; | |
template <typename Function> | |
typename function_traits<Function>::function | |
to_function (Function& lambda) | |
{ | |
return static_cast<typename function_traits<Function>::function>(lambda); | |
} | |
// Aspect logging duration of execution | |
template <typename R, typename ...Args> | |
std::function<R(Args...)> logged(string name, std::function<R(Args...)> f) | |
{ | |
return [f,name](Args... args){ | |
LOG << name << " start" << NL; | |
auto start = std::chrono::high_resolution_clock::now(); | |
R result = f(std::forward<Args>(args)...); | |
auto end = std::chrono::high_resolution_clock::now(); | |
auto total = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); | |
LOG << "Elapsed: " << total << "us" << NL; | |
return result; | |
}; | |
} | |
// Security checking | |
template <typename R, typename ...Args, typename S> | |
std::function<Maybe<R>(Args...)> secured(S session, std::function<Maybe<R>(Args...)> f) | |
{ | |
// if user is not valid - return nothing | |
return [f, &session](Args... args) -> Maybe<R> { | |
if (session.isValid()) | |
return f(std::forward<Args>(args)...); | |
else | |
return Error(403, "Forbidden"); | |
}; | |
} | |
// Use local cache (memoize) | |
template <typename R, typename C, typename ...Args> | |
std::function<Maybe<R>(Args...)> cached(C & cache, std::function<Maybe<R>(Args...)> f) | |
{ | |
return [f,&cache](Args... args){ | |
// get key as tuple of arguments | |
auto key = make_tuple(args...); | |
if (cache.count(key) > 0) | |
return just(cache[key]); | |
else | |
{ | |
Maybe<R> result = f(std::forward<Args>(args)...); | |
if (!result.hasError()) | |
cache.insert(std::pair<decltype(key), R>(key, result())); //add to cache | |
return result; | |
} | |
}; | |
} | |
// If there was error - try again | |
template <typename R, typename ...Args> | |
std::function<Maybe<R>(Args...)> triesTwice(std::function<Maybe<R>(Args...)> f) | |
{ | |
return [f](Args... args){ | |
Maybe<R> result = f(std::forward<Args>(args)...); | |
if (result.hasError()) | |
return f(std::forward<Args>(args)...); | |
return result; | |
}; | |
} | |
// Treat empty state as error | |
template <typename R, typename ...Args> | |
std::function<Maybe<R>(Args...)> notEmpty(std::function<Maybe<R>(Args...)> f) | |
{ | |
return [f](Args... args) -> Maybe<R> { | |
Maybe<R> result = f(std::forward<Args>(args)...); | |
if ((!result.hasError()) && (result.isEmpty())) | |
return Error(404, "Not Found"); | |
return result; | |
}; | |
} | |
template <typename R, typename ...Args> | |
std::function<R(Args...)> locked(std::mutex& m, std::function<R(Args...)> f) | |
{ | |
return [f,&m](Args... args){ | |
std::unique_lock<std::mutex> lock(m); | |
return f(std::forward<Args>(args)...); | |
}; | |
} | |
// Couple of additional helpers | |
template <class F, class... Args> | |
void for_each_argument(F f, Args&&... args) { | |
(void)(int[]){(f(forward<Args>(args)), 0)...}; | |
} | |
template <class T, class... P> | |
inline auto make(P&&... args) -> T { | |
return std::make_shared<typename T::element_type>(std::forward<P>(args)...); | |
} | |
int main() { | |
// Database... | |
vector<User> users {make<User>(1, "John", 0), make<User>(2, "Bob", 1), make<User>(3, "Max", 1)}; | |
// Request method | |
auto findUser = [&users](int id) -> Maybe<User> { | |
for (User user : users) { | |
if (user->id == id) | |
return user; | |
} | |
return nullptr; | |
}; | |
// Local cache | |
map<tuple<int>,User> userCache; | |
// Security | |
class Session { | |
public: | |
bool isValid() { return true; } | |
} session; | |
// Mutex to test locked aspect | |
std::mutex lockMutex; | |
// Main functional factorization | |
auto findUserFinal = locked(lockMutex, secured(session, notEmpty( cached(userCache, triesTwice( logged("findUser", to_function(findUser))))))); | |
// TEST: | |
auto testUser = [&](int id) { | |
auto user = findUserFinal(id); | |
LOG << (user.hasError() ? "ERROR: " + user.getError()->message : "NAME:" + user()->name) << NL; | |
}; | |
for_each_argument(testUser, 2, 30, 2, 1); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment