diff --git a/src/util/lexcmp_def.hpp b/src/util/lexcmp_def.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d1c3e8a3bfc63341070b55b1320de03583797bbe
--- /dev/null
+++ b/src/util/lexcmp_def.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+/*
+ * Macro definitions for defining comparison operators for
+ * record-like types.
+ *
+ * Use:
+ *
+ * To define comparison operations for a record type xyzzy
+ * with fields foo, bar and baz:
+ *
+ * DEFINE_LEXICOGRAPHIC_ORDERING(xyzzy,(a.foo,a.bar,a.baz),(b.foo,b.bar,b.baz))
+ *
+ * The explicit use of 'a' and 'b' in the second and third parameters
+ * is needed only to save a heroic amount of preprocessor macro
+ * deep magic.
+ *
+ */
+
+#include <tuple>
+
+#define DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(proxy,op,type,a_fields,b_fields) \
+inline bool operator op(const type &a,const type &b) { return proxy a_fields op proxy b_fields; }
+
+#define DEFINE_LEXICOGRAPHIC_ORDERING(type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,<,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,>,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,<=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,>=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,!=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,==,type,a_fields,b_fields)
+
+#define DEFINE_LEXICOGRAPHIC_ORDERING_BY_VALUE(type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,<,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,>,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,<=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,>=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,!=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,==,type,a_fields,b_fields)
+
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index a3a2d55333f6316b133bff3982ec58e1b03d525d..61d713bc831e7ec63dfaf370f7931e204c7d9e45 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -16,6 +16,7 @@ set(TEST_SOURCES
     test_event_queue.cpp
     test_fvm.cpp
     test_cell_group.cpp
+    test_lexcmp.cpp
     test_matrix.cpp
     test_mechanisms.cpp
     test_optional.cpp
diff --git a/tests/unit/test_lexcmp.cpp b/tests/unit/test_lexcmp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a02ac0765823f61d54e8acee3cf38611552d9dd7
--- /dev/null
+++ b/tests/unit/test_lexcmp.cpp
@@ -0,0 +1,110 @@
+#include "gtest.h"
+
+#include <util/lexcmp_def.hpp>
+
+struct lexcmp_test_one {
+    int foo;
+};
+
+DEFINE_LEXICOGRAPHIC_ORDERING(lexcmp_test_one, (a.foo), (b.foo))
+
+TEST(lexcmp_def,one) {
+    lexcmp_test_one p{3}, q{4}, r{4};
+
+    EXPECT_LE(p,q);
+    EXPECT_LT(p,q);
+    EXPECT_NE(p,q);
+    EXPECT_GE(q,p);
+    EXPECT_GT(q,p);
+
+    EXPECT_LE(q,r);
+    EXPECT_GE(q,r);
+    EXPECT_EQ(q,r);
+}
+
+struct lexcmp_test_three {
+    int x;
+    std::string y;
+    double z;
+};
+
+// test fields in reverse order: z, y, x
+DEFINE_LEXICOGRAPHIC_ORDERING(lexcmp_test_three, (a.z,a.y,a.x), (b.z,b.y,b.x))
+
+TEST(lexcmp_def,three) {
+    lexcmp_test_three p{1,"foo",2};
+    lexcmp_test_three q{1,"foo",3};
+    lexcmp_test_three r{1,"bar",2};
+    lexcmp_test_three s{5,"foo",2};
+
+    EXPECT_LE(p,q);
+    EXPECT_LT(p,q);
+    EXPECT_NE(p,q);
+    EXPECT_GE(q,p);
+    EXPECT_GT(q,p);
+
+    EXPECT_LE(r,p);
+    EXPECT_LT(r,p);
+    EXPECT_NE(p,r);
+    EXPECT_GE(p,r);
+    EXPECT_GT(p,r);
+
+    EXPECT_LE(p,s);
+    EXPECT_LT(p,s);
+    EXPECT_NE(p,s);
+    EXPECT_GE(s,p);
+    EXPECT_GT(s,p);
+}
+
+// test fields accessed by reference-returning member function
+
+class lexcmp_test_refmemfn {
+public:
+    explicit lexcmp_test_refmemfn(int foo): foo_(foo) {}
+
+    const int &foo() const { return foo_; }
+    int &foo() { return foo_; }
+
+private:
+    int foo_;
+};
+
+DEFINE_LEXICOGRAPHIC_ORDERING(lexcmp_test_refmemfn, (a.foo()), (b.foo()))
+
+TEST(lexcmp_def,refmemfn) {
+    lexcmp_test_refmemfn p{3};
+    const lexcmp_test_refmemfn q{4};
+
+    EXPECT_LE(p,q);
+    EXPECT_LT(p,q);
+    EXPECT_NE(p,q);
+    EXPECT_GE(q,p);
+    EXPECT_GT(q,p);
+}
+
+// test comparison via proxy tuple object
+
+class lexcmp_test_valmemfn {
+public:
+    explicit lexcmp_test_valmemfn(int foo, int bar): foo_(foo), bar_(bar) {}
+    int foo() const { return foo_; }
+    int bar() const { return bar_; }
+
+private:
+    int foo_;
+    int bar_;
+};
+
+DEFINE_LEXICOGRAPHIC_ORDERING_BY_VALUE(lexcmp_test_valmemfn, (a.foo(),a.bar()), (b.foo(),b.bar()))
+
+TEST(lexcmp_def,proxy) {
+    lexcmp_test_valmemfn p{3,2}, q{3,4};
+
+    EXPECT_LE(p,q);
+    EXPECT_LT(p,q);
+    EXPECT_NE(p,q);
+    EXPECT_GE(q,p);
+    EXPECT_GT(q,p);
+}
+
+