Skip to content

Instantly share code, notes, and snippets.

@mtao
Created June 9, 2020 00:03
Show Gist options
  • Save mtao/5fc4f788278c53409174d039024bf977 to your computer and use it in GitHub Desktop.
Save mtao/5fc4f788278c53409174d039024bf977 to your computer and use it in GitHub Desktop.
a quick/dirty implementation of a return value caching mechanism
#include <functional>
#include <iostream>
#include <map>
#include <set>
#include <tuple>
// all caching classes need to have a basic inventory of dirty flags and a tool
// to flush them.
class CachableType {
template <typename RetType, typename... Args>
friend class Cachable;
protected:
// typically only members that dirty the data should use this function
// other than that
void mark_dirty() {
for (auto&& f : dirty_flags) {
*f = true;
}
}
private:
std::set<bool*> dirty_flags;
};
// this struct keeps track of the cache for each function
template <typename RetType, typename... Args>
class Cachable {
public:
using FuncType = std::function<RetType(Args...)>;
Cachable(FuncType&& f, CachableType& cat) : _func(f) {
cat.dirty_flags.insert(&_dirty_flag);
}
// template <typename Class>
// Cachable(const Class& c): Cachable{c.dirty_flag()} {}
RetType operator()(Args... args) {
if (_dirty_flag || !_map.contains(std::make_tuple(args...))) {
if (_dirty_flag) {
_map.clear();
_dirty_flag = false;
}
if constexpr (std::is_same_v<RetType, void>) {
_map.emplace(std::make_tuple(args...));
_func(std::forward<Args>(args)...);
} else {
return _map[std::make_tuple(args...)] =
_func(std::forward<Args>(args)...);
}
} else {
if constexpr (std::is_same_v<RetType, void>) {
} else {
return _map[std::make_tuple(args...)];
}
}
}
private:
FuncType _func;
std::conditional_t<std::is_same_v<RetType, void>,
std::set<std::tuple<std::decay_t<Args>...>>,
std::map<std::tuple<std::decay_t<Args>...>, RetType>>
_map;
bool _dirty_flag = true;
};
// this macro declares the Cachable member functor in the class, using
// caching_{function name}
#define MAKE_CACHABLE(NAME, RETTYPE, ...) \
Cachable<RETTYPE, ##__VA_ARGS__> caching_##NAME = \
Cachable<RETTYPE, ##__VA_ARGS__>{ \
std::bind_front(&std::decay_t<decltype(*this)>::NAME, this), \
*this};
// a convenience function so we dont need a declaration + cachable declaration
#define CACHABLE_MEMBER_DECLARATION(NAME, RETTYPE, ...) \
RETTYPE NAME(__VA_ARGS__) const; \
MAKE_CACHABLE(NAME, RETTYPE, ##__VA_ARGS__)
// similar to CACHABLE_MEMBER_DECLARATION
// idempotent members lack const-ness but will only be called once per dirty
// its really the same as cachable, just allowing for non-constness :P
#define IDEMPOTENT_MEMBER_DECLARATION(NAME, RETTYPE, ...) \
RETTYPE NAME(__VA_ARGS__); \
MAKE_CACHABLE(NAME, RETTYPE, ##__VA_ARGS__)
class A : public CachableType {
public:
using CachableType::mark_dirty;
IDEMPOTENT_MEMBER_DECLARATION(f, void, int)
CACHABLE_MEMBER_DECLARATION(g, int)
CACHABLE_MEMBER_DECLARATION(h, int, int, const std::string&)
};
void A::f(int a) { std::cout << "calling f(" << a << ")" << std::endl; }
int A::g() const {
std::cout << "calling g()" << std::endl;
return 5;
}
int A::h(int val, const std::string& str) const {
std::cout << "calling h(" << val << "," << str << ")" << std::endl;
return 5 * val;
}
int main(int argc, char* argv[]) {
A a;
std::cout << "f should be called for 3,4,5 but not for 3 a second time"
<< std::endl;
a.caching_f(3);
a.caching_f(4);
a.caching_f(5);
a.caching_f(3);
std::cout << "I should only call g once but see 5 twice" << std::endl;
std::cout << a.caching_g() << std::endl;
std::cout << a.caching_g() << std::endl;
std::cout << "I should only call h twice, but see 2 printed twice then 3 "
"printed twice"
<< std::endl;
std::cout << a.caching_h(2, "a") << std::endl;
std::cout << a.caching_h(2, "a") << std::endl;
std::cout << a.caching_h(3, "a") << std::endl;
std::cout << a.caching_h(3, "a") << std::endl;
std::cout << "Should call h once and then just return from cache"
<< std::endl;
std::cout << a.caching_h(3, "b") << std::endl;
std::cout << a.caching_h(3, "b") << std::endl;
a.mark_dirty();
std::cout << "These next 3 calls should invoke" << std::endl;
a.caching_f(2);
std::cout << a.caching_g() << std::endl;
std::cout << a.caching_h(3, "a") << std::endl;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment