diff --git a/src/util/lexcmp_def.hpp b/src/util/lexcmp_def.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0cc7e66d0b9cdce347a7b5bac9d89fb50fe0de5b
--- /dev/null
+++ b/src/util/lexcmp_def.hpp
@@ -0,0 +1,31 @@
+#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_(op,type,a_fields,b_fields) \
+inline bool operator op(const type &a,const type &b) { return std::tie a_fields op std::tie b_fields; }
+
+#define DEFINE_LEXICOGRAPHIC_ORDERING(type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(<,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(>,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(<=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(>=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(!=,type,a_fields,b_fields) \
+DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(==,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..300372391de1d69016eec827cf03ef1e46c30dfb
--- /dev/null
+++ b/tests/unit/test_lexcmp.cpp
@@ -0,0 +1,58 @@
+#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);
+}
+