diff --git a/src/event_queue.hpp b/src/event_queue.hpp
index 31f6a5a10cf6b0c9ff9c0ff049909b801cd25b34..2a4c7ec7a21ce58460bb8d121c4b0c50df52789b 100644
--- a/src/event_queue.hpp
+++ b/src/event_queue.hpp
@@ -3,18 +3,20 @@
 #include <cstdint>
 #include <ostream>
 #include <queue>
+#include <type_traits>
 
 #include "common_types.hpp"
+#include "util/meta.hpp"
 #include "util/optional.hpp"
 
 namespace nest {
 namespace mc {
 
-/* An event class Event must comply with the following conventions:
- * Typedefs:
- *     time_type               floating point type used to represent event times
- * Member functions:
- *     time_type when() const  return time value associated with event
+/* Event classes `Event` used with `event_queue` must be move and copy constructible,
+ * and either have a public field `time` that returns the time value, or provide an
+ * overload of `event_time(const Event&)` which returns this value.
+ *
+ * Time values must be well ordered with respect to `operator>`.
  */
 
 template <typename Time>
@@ -24,8 +26,6 @@ struct postsynaptic_spike_event {
     cell_member_type target;
     time_type time;
     float weight;
-
-    time_type when() const { return time; }
 };
 
 template <typename Time>
@@ -34,53 +34,58 @@ struct sample_event {
 
     std::uint32_t sampler_index;
     time_type time;
-
-    time_type when() const { return time; }
 };
 
-/* Event objects must have a method event_time() which returns a value
- * from a type with a total ordering with respect to <, >, etc.
- */
+// Configuration point: define `event_time(ev)` for event objects `ev`
+// that do not have the corresponding `time` member field.
+
+template <typename Event>
+auto event_time(const Event& ev) -> decltype(ev.time) {
+    return ev.time;
+}
+
+namespace impl {
+    using ::nest::mc::event_time;
+
+    // wrap in `impl::` namespace to obtain correct ADL for return type.
+    template <typename Event>
+    using event_time_type = decltype(event_time(std::declval<Event>()));
+}
 
 template <typename Event>
 class event_queue {
 public :
     using value_type = Event;
-    using time_type = typename Event::time_type;
+    using time_type = impl::event_time_type<Event>;
 
-    // create
     event_queue() {}
 
-    // push stuff
-    template <typename Iter>
-    void push(Iter b, Iter e) {
-         for (; b!=e; ++b) {
-             queue_.push(*b);
-         }
-    }
-
-    // push thing
     void push(const value_type& e) {
          queue_.push(e);
     }
 
+    bool empty() const {
+        return size()==0;
+    }
+
     std::size_t size() const {
         return queue_.size();
     }
 
-    // pop until
-    util::optional<value_type> pop_if_before(time_type t_until) {
-         if (!queue_.empty() && queue_.top().when() < t_until) {
-             auto ev = queue_.top();
-             queue_.pop();
-             return ev;
-         }
-         else {
-             return util::nothing;
-         }
+    // Pop and return top event `ev` of queue if `t_until` > `event_time(ev)`.
+    util::optional<value_type> pop_if_before(const time_type& t_until) {
+        using ::nest::mc::event_time;
+        if (!queue_.empty() && t_until > event_time(queue_.top())) {
+            auto ev = queue_.top();
+            queue_.pop();
+            return ev;
+        }
+        else {
+            return util::nothing;
+        }
     }
 
-    // clear everything
+    // Clear queue and free storage.
     void clear() {
         queue_ = decltype(queue_){};
     }
@@ -88,7 +93,8 @@ public :
 private:
     struct event_greater {
         bool operator()(const Event& a, const Event& b) {
-            return a.when() > b.when();
+            using ::nest::mc::event_time;
+            return event_time(a) > event_time(b);
         }
     };
 
diff --git a/tests/unit/test_event_queue.cpp b/tests/unit/test_event_queue.cpp
index 81b24752401d804a5534d8b55384c3784491e485..6a6752b651bbd1538682a7f48f1535d9a06e1757 100644
--- a/tests/unit/test_event_queue.cpp
+++ b/tests/unit/test_event_queue.cpp
@@ -1,5 +1,6 @@
 #include "../gtest.h"
 
+#include <cmath>
 #include <vector>
 
 #include <event_queue.hpp>
@@ -16,38 +17,12 @@ TEST(event_queue, push)
     q.push({{8u, 2u}, 20.f, 2.f});
     q.push({{2u, 3u}, 8.f, 2.f});
 
-    std::vector<float> times;
-    while(q.size()) {
-        times.push_back(
-            q.pop_if_before(std::numeric_limits<float>::max())->time
-        );
-    }
-
-    //std::copy(times.begin(), times.end(), std::ostream_iterator<float>(std::cout, ","));
-    //std::cout << "\n";
-    EXPECT_TRUE(std::is_sorted(times.begin(), times.end()));
-}
-
-TEST(event_queue, push_range)
-{
-    using namespace nest::mc;
-    using ps_event_queue = event_queue<postsynaptic_spike_event<float>>;
-
-    postsynaptic_spike_event<float> events[] = {
-        {{1u, 0u}, 2.f, 2.f},
-        {{4u, 1u}, 1.f, 2.f},
-        {{8u, 2u}, 20.f, 2.f},
-        {{2u, 3u}, 8.f, 2.f}
-    };
-
-    ps_event_queue q;
-    q.push(std::begin(events), std::end(events));
+    EXPECT_EQ(4u, q.size());
 
     std::vector<float> times;
-    while(q.size()) {
-        times.push_back(
-            q.pop_if_before(std::numeric_limits<float>::max())->time
-        );
+    float maxtime(INFINITY);
+    while (!q.empty()) {
+        times.push_back(q.pop_if_before(maxtime)->time);
     }
 
     EXPECT_TRUE(std::is_sorted(times.begin(), times.end()));
@@ -73,37 +48,92 @@ TEST(event_queue, pop_if_before)
     };
 
     ps_event_queue q;
-    q.push(std::begin(events), std::end(events));
+    for (const auto& ev: events) {
+        q.push(ev);
+    }
 
-    EXPECT_EQ(q.size(), 4u);
+    EXPECT_EQ(4u, q.size());
 
     auto e1 = q.pop_if_before(0.);
     EXPECT_FALSE(e1);
-    EXPECT_EQ(q.size(), 4u);
+    EXPECT_EQ(4u, q.size());
 
     auto e2 = q.pop_if_before(5.);
     EXPECT_TRUE(e2);
     EXPECT_EQ(e2->target, target[0]);
-    EXPECT_EQ(q.size(), 3u);
+    EXPECT_EQ(3u, q.size());
 
     auto e3 = q.pop_if_before(5.);
     EXPECT_TRUE(e3);
     EXPECT_EQ(e3->target, target[1]);
-    EXPECT_EQ(q.size(), 2u);
+    EXPECT_EQ(2u, q.size());
 
     auto e4 = q.pop_if_before(2.5);
     EXPECT_FALSE(e4);
-    EXPECT_EQ(q.size(), 2u);
+    EXPECT_EQ(2u, q.size());
 
     auto e5 = q.pop_if_before(5.);
     EXPECT_TRUE(e5);
     EXPECT_EQ(e5->target, target[2]);
-    EXPECT_EQ(q.size(), 1u);
+    EXPECT_EQ(1u, q.size());
 
     q.pop_if_before(5.);
-    EXPECT_EQ(q.size(), 0u);
+    EXPECT_EQ(0u, q.size());
+    EXPECT_TRUE(q.empty());
 
     // empty queue should always return "false"
     auto e6 = q.pop_if_before(100.);
     EXPECT_FALSE(e6);
 }
+
+// Event queues can be defined for arbitrary copy-constructible events
+// for which `event_time(ev)` returns the corresponding time. Time values just
+// need to be well-ordered on '>'.
+
+struct wrapped_float {
+    wrapped_float() {}
+    wrapped_float(float f): f(f) {}
+
+    float f;
+    bool operator>(wrapped_float x) const { return f>x.f; }
+};
+
+struct minimal_event {
+    wrapped_float value;
+    explicit minimal_event(float x): value(x) {}
+};
+
+const wrapped_float& event_time(const minimal_event& ev) { return ev.value; }
+
+TEST(event_queue, minimal_event_impl)
+{
+    using nest::mc::event_queue;
+
+    minimal_event events[] = {
+        minimal_event(3.f),
+        minimal_event(2.f),
+        minimal_event(2.f),
+        minimal_event(10.f)
+    };
+
+    std::vector<float> expected;
+    for (const auto& ev: events) {
+        expected.push_back(ev.value.f);
+    }
+    std::sort(expected.begin(), expected.end());
+
+    event_queue<minimal_event> q;
+    for (auto& ev: events) {
+        q.push(ev);
+    }
+
+    wrapped_float maxtime(INFINITY);
+
+    std::vector<float> times;
+    while (q.size()) {
+        times.push_back(q.pop_if_before(maxtime)->value.f);
+    }
+
+    EXPECT_EQ(expected, times);
+}
+