Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
C++11 functional decomposition - easy way to do AOP
// 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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.