An expirable LRU cache implementation I implemented an ExpireLRUCache class, which will clear the data when it times out. There are two ways to achieve this:

Use a timer to clear the expire data
Call the clean function in Add and Get

If I use a struct in class, and it use the template typename, how to deal with this?
I put the declaration of Node at the top, because it will be used when it is used. Is it reasonable?
template &lt;typename K, typename V&gt;
class ExpireLRUCache {
 private:
  using Timestamp = std::chrono::time_point&lt;std::chrono::system_clock&gt;;
  struct Node {
    K key;
    V value;
    Timestamp timestamp;
  };

 public:
  using NodePtr = std::shared_ptr&lt;Node&gt;;
  using NodeIter = typename std::list&lt;NodePtr&gt;::iterator;
  using ExpiredCallBack = std::function&lt;void(K, V)&gt;;
  
  // Default timeout is 3000ms.
  ExpireLRUCache(size_t max_size)
     : max_size_(max_size), time_out_(3000), expired_callback_(nullptr) {}

  ExpireLRUCache(size_t max_size, uint32_t time_out, ExpiredCallBack call_back)
     : max_size_(max_size), time_out_(time_out), expired_callback_(call_back) {}

  void Add(K key, V value);
  V Get(K key);

  size_t Size() const;

 private:
  void Expired();

  mutable std::mutex mutex_;
  std::list&lt;NodePtr&gt; list_;
  std::unordered_map&lt;K, NodeIter&gt; map_;

  size_t max_size_;
  // ms
  uint32_t time_out_;

  ExpiredCallBack expired_callback_;  
};

template &lt;typename K, typename V&gt;
void ExpireLRUCache&lt;K, V&gt;::Add(K key, V value) {
  std::lock_guard&lt;std::mutex&gt; lock(mutex_);
  // if full, delete oldest
  if (list_.size() &gt;= max_size_) {
    auto oldest = list_.back();
    list_.pop_back();
    map_.erase(oldest-&gt;key);
  }

  // if exist, delete it in the list, and then add to the front
  // then overwrite in map.
  if (map_.find(key) != map_.end()) {
    NodeIter iter = map_[key];
    list_.erase(iter);
  }

  auto timestamp = std::chrono::system_clock::now();
  NodePtr node = std::make_shared&lt;Node&gt;(Node{key, value, timestamp});
  list_.push_front(node);
  map_[key] = list_.begin();
}

template &lt;typename K, typename V&gt;
V ExpireLRUCache&lt;K, V&gt;::Get(K key) {
  std::lock_guard&lt;std::mutex&gt; lock(mutex_);
  
  // Todo(zero): how to call
  Expired();

  if (map_.find(key) != map_.end()) {
    return (*map_[key])-&gt;value;
  } else {
    return V{};
  }
}

template &lt;typename K, typename V&gt;
void ExpireLRUCache&lt;K, V&gt;::Expired() {
  auto time_now = std::chrono::system_clock::now();

  while( !list_.empty() ) {
    auto oldest = list_.back();
    auto diff = std::chrono::duration_cast&lt;std::chrono::milliseconds&gt;(
                    time_now - oldest-&gt;timestamp);
    if (diff.count() &gt; time_out_) {
      list_.pop_back();
      map_.erase(oldest-&gt;key);
      expired_callback_(oldest-&gt;key, oldest-&gt;value);
    } else {
      break;
    }
  }
}

template &lt;typename K, typename V&gt;
size_t ExpireLRUCache&lt;K, V&gt;::Size() const {
  std::lock_guard&lt;std::mutex&gt; lock(mutex_);
  return map_.size();
}
```
I don't see why the typenames NodePtr and NodeIter are public.  Node itself is private, and none of the public interface uses these two types, so I think they should be private, too.

I'm not sure I like this interface:
V Get(K key);

This means we can only cache value types.  Not only that, but there's no way to indicate &quot;not in the cache&quot;; I see the implementation default-constructs a return value in that case - further limiting the types that can be used.

Expired() expects that the mutex is held, but is missing a comment stating that, so it took too long to confirm that it's used correctly.

What's going on here?

NodePtr node = std::make_shared&lt;Node&gt;(Node{key, value, timestamp});


Why construct a Node and pass it to make_shared() to copy?  It would be better to make_shared() directly:
NodePtr node = std::make_shared&lt;Node&gt;(key, value, timestamp);