Skip to content

Instantly share code, notes, and snippets.

@VictorLaskin
Last active November 17, 2021 12:28
Show Gist options
  • Save VictorLaskin/1b973014b1c3aeab228e to your computer and use it in GitHub Desktop.
Save VictorLaskin/1b973014b1c3aeab228e to your computer and use it in GitHub Desktop.
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