diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt
index 5f135d92ab3d6816b2a02777fbbe8f656f851356..3d3f25f1790b634493147077fae655aeb9cdcc42 100644
--- a/arbor/CMakeLists.txt
+++ b/arbor/CMakeLists.txt
@@ -54,6 +54,7 @@ set(arbor_sources
     spike_event_io.cpp
     spike_source_cell_group.cpp
     s_expr.cpp
+    symmetric_recipe.cpp
     threading/threading.cpp
     thread_private_spike_store.cpp
     tree.cpp
diff --git a/arbor/communication/mpi_error.cpp b/arbor/communication/mpi_error.cpp
index 4f0b62c54e8f00c99e8330d4ba20c625eccf73b6..de511c216673245531e696b7cf6bf39090f549d1 100644
--- a/arbor/communication/mpi_error.cpp
+++ b/arbor/communication/mpi_error.cpp
@@ -9,4 +9,19 @@ const mpi_error_category_impl& mpi_error_category() {
     return the_category;
 }
 
+const char* mpi_error_category_impl::name() const noexcept { return "MPI"; }
+
+std::string mpi_error_category_impl::message(int ev) const {
+    char err[MPI_MAX_ERROR_STRING];
+    int r;
+    MPI_Error_string(ev, err, &r);
+    return err;
+}
+
+std::error_condition mpi_error_category_impl::default_error_condition(int ev) const noexcept {
+    int eclass;
+    MPI_Error_class(ev, &eclass);
+    return std::error_condition(eclass, mpi_error_category());
 }
+
+} // namespace arb
diff --git a/arbor/cv_policy.cpp b/arbor/cv_policy.cpp
index 468d6a7067cee907583d578535b9ca8330053522..65deddcede9cec00a801933c99b9bdbfbff9890a 100644
--- a/arbor/cv_policy.cpp
+++ b/arbor/cv_policy.cpp
@@ -65,6 +65,7 @@ cv_policy operator|(const cv_policy& lhs, const cv_policy& rhs) {
 
 // Public policy implementations:
 
+// cv_policy_explicit
 locset cv_policy_explicit::cv_boundary_points(const cable_cell& cell) const {
     return
         ls::support(
@@ -76,6 +77,24 @@ locset cv_policy_explicit::cv_boundary_points(const cable_cell& cell) const {
                 components(cell.morphology(), thingify(domain_, cell.provider()))));
 }
 
+cv_policy_base_ptr cv_policy_explicit::clone() const {
+    return cv_policy_base_ptr(new cv_policy_explicit(*this));
+}
+
+region cv_policy_explicit::domain() const { return domain_; }
+
+// cv_policy_single
+locset cv_policy_single::cv_boundary_points(const cable_cell&) const {
+    return ls::cboundary(domain_);
+}
+
+cv_policy_base_ptr cv_policy_single::clone() const {
+    return cv_policy_base_ptr(new cv_policy_single(*this));
+}
+
+region cv_policy_single::domain() const { return domain_; }
+
+// cv_policy_max_extent
 locset cv_policy_max_extent::cv_boundary_points(const cable_cell& cell) const {
     const unsigned nbranch = cell.morphology().num_branches();
     const auto& embed = cell.embedding();
@@ -109,6 +128,13 @@ locset cv_policy_max_extent::cv_boundary_points(const cable_cell& cell) const {
     return unique_sum(locset(std::move(points)), ls::cboundary(domain_));
 }
 
+cv_policy_base_ptr cv_policy_max_extent::clone() const {
+    return cv_policy_base_ptr(new cv_policy_max_extent(*this));
+}
+
+region cv_policy_max_extent::domain() const { return domain_; }
+
+// cv_policy_fixed_per_branch
 locset cv_policy_fixed_per_branch::cv_boundary_points(const cable_cell& cell) const {
     const unsigned nbranch = cell.morphology().num_branches();
     if (!nbranch) return ls::nil();
@@ -139,6 +165,14 @@ locset cv_policy_fixed_per_branch::cv_boundary_points(const cable_cell& cell) co
     return unique_sum(locset(std::move(points)), ls::cboundary(domain_));
 }
 
+cv_policy_base_ptr cv_policy_fixed_per_branch::clone() const {
+    return cv_policy_base_ptr(new cv_policy_fixed_per_branch(*this));
+}
+
+region cv_policy_fixed_per_branch::domain() const { return domain_; }
+
+
+// cv_policy_every_segment
 locset cv_policy_every_segment::cv_boundary_points(const cable_cell& cell) const {
     const unsigned nbranch = cell.morphology().num_branches();
     if (!nbranch) return ls::nil();
@@ -147,5 +181,10 @@ locset cv_policy_every_segment::cv_boundary_points(const cable_cell& cell) const
                 ls::cboundary(domain_),
                 ls::restrict(ls::segment_boundaries(), domain_));
 }
+cv_policy_base_ptr cv_policy_every_segment::clone() const {
+    return cv_policy_base_ptr(new cv_policy_every_segment(*this));
+}
+
+region cv_policy_every_segment::domain() const { return domain_; }
 
 } // namespace arb
diff --git a/arbor/include/arbor/communication/mpi_error.hpp b/arbor/include/arbor/communication/mpi_error.hpp
index fcbfc50b5070dc258fc9f40386df3c4708213f70..911447a1a0357e20e278709704769c5d32101cc7 100644
--- a/arbor/include/arbor/communication/mpi_error.hpp
+++ b/arbor/include/arbor/communication/mpi_error.hpp
@@ -82,18 +82,9 @@ class mpi_error_category_impl;
 const mpi_error_category_impl& mpi_error_category();
 
 class mpi_error_category_impl: public std::error_category {
-    const char* name() const noexcept override { return "MPI"; }
-    std::string message(int ev) const override {
-        char err[MPI_MAX_ERROR_STRING];
-        int r;
-        MPI_Error_string(ev, err, &r);
-        return err;
-    }
-    std::error_condition default_error_condition(int ev) const noexcept override {
-        int eclass;
-        MPI_Error_class(ev, &eclass);
-        return std::error_condition(eclass, mpi_error_category());
-    }
+    const char* name() const noexcept override;
+    std::string message(int) const override;
+    std::error_condition default_error_condition(int) const noexcept override;
 };
 
 inline std::error_condition make_error_condition(mpi_errc ec) {
diff --git a/arbor/include/arbor/cv_policy.hpp b/arbor/include/arbor/cv_policy.hpp
index b8869f02e36c1cf22684a5996aa929c800de85e6..4ddbdfcb95e7d8b26d326d1b6e764aac97789240 100644
--- a/arbor/include/arbor/cv_policy.hpp
+++ b/arbor/include/arbor/cv_policy.hpp
@@ -114,12 +114,9 @@ struct cv_policy_explicit: cv_policy_base {
     explicit cv_policy_explicit(locset locs, region domain = reg::all()):
         locs_(std::move(locs)), domain_(std::move(domain)) {}
 
-    cv_policy_base_ptr clone() const override {
-        return cv_policy_base_ptr(new cv_policy_explicit(*this));
-    }
-
+    cv_policy_base_ptr clone() const override;
     locset cv_boundary_points(const cable_cell&) const override;
-    region domain() const override { return domain_; }
+    region domain() const override;
 
 private:
     locset locs_;
@@ -130,14 +127,9 @@ struct cv_policy_single: cv_policy_base {
     explicit cv_policy_single(region domain = reg::all()):
         domain_(domain) {}
 
-    cv_policy_base_ptr clone() const override {
-        return cv_policy_base_ptr(new cv_policy_single(*this));
-    }
-
-    locset cv_boundary_points(const cable_cell&) const override {
-        return ls::cboundary(domain_);
-    }
-    region domain() const override { return domain_; }
+    cv_policy_base_ptr clone() const override;
+    locset cv_boundary_points(const cable_cell&) const override;
+    region domain() const override;
 
 private:
     region domain_;
@@ -150,12 +142,9 @@ struct cv_policy_max_extent: cv_policy_base {
     explicit cv_policy_max_extent(double max_extent, cv_policy_flag::value flags = cv_policy_flag::none):
          max_extent_(max_extent), domain_(reg::all()), flags_(flags) {}
 
-    cv_policy_base_ptr clone() const override {
-        return cv_policy_base_ptr(new cv_policy_max_extent(*this));
-    }
-
+    cv_policy_base_ptr clone() const override;
     locset cv_boundary_points(const cable_cell&) const override;
-    region domain() const override { return domain_; }
+    region domain() const override;
 
 private:
     double max_extent_;
@@ -170,12 +159,9 @@ struct cv_policy_fixed_per_branch: cv_policy_base {
     explicit cv_policy_fixed_per_branch(unsigned cv_per_branch, cv_policy_flag::value flags = cv_policy_flag::none):
          cv_per_branch_(cv_per_branch), domain_(reg::all()), flags_(flags) {}
 
-    cv_policy_base_ptr clone() const override {
-        return cv_policy_base_ptr(new cv_policy_fixed_per_branch(*this));
-    }
-
+    cv_policy_base_ptr clone() const override;
     locset cv_boundary_points(const cable_cell&) const override;
-    region domain() const override { return domain_; }
+    region domain() const override;
 
 private:
     unsigned cv_per_branch_;
@@ -187,12 +173,9 @@ struct cv_policy_every_segment: cv_policy_base {
     explicit cv_policy_every_segment(region domain = reg::all()):
          domain_(std::move(domain)) {}
 
-    cv_policy_base_ptr clone() const override {
-        return cv_policy_base_ptr(new cv_policy_every_segment(*this));
-    }
-
+    cv_policy_base_ptr clone() const override;
     locset cv_boundary_points(const cable_cell&) const override;
-    region domain() const override { return domain_; }
+    region domain() const override;
 
 private:
     region domain_;
diff --git a/arbor/include/arbor/symmetric_recipe.hpp b/arbor/include/arbor/symmetric_recipe.hpp
index 7addcf6fed71434325d763a61c41857a0101428b..81c8336e77e48ef2fbbdfa6c6cce195cd8bfc9d4 100644
--- a/arbor/include/arbor/symmetric_recipe.hpp
+++ b/arbor/include/arbor/symmetric_recipe.hpp
@@ -1,10 +1,6 @@
 #pragma once
 
 #include <any>
-#include <cstddef>
-#include <memory>
-#include <unordered_map>
-#include <stdexcept>
 
 #include <arbor/recipe.hpp>
 #include <arbor/util/unique_any.hpp>
@@ -29,56 +25,23 @@ class symmetric_recipe: public recipe {
 public:
     symmetric_recipe(std::unique_ptr<tile> rec): tiled_recipe_(std::move(rec)) {}
 
-    cell_size_type num_cells() const override {
-        return tiled_recipe_->num_cells() * tiled_recipe_->num_tiles();
-    }
+    cell_size_type num_cells() const override;
 
-    util::unique_any get_cell_description(cell_gid_type i) const override {
-        return tiled_recipe_->get_cell_description(i % tiled_recipe_->num_cells());
-    }
+    util::unique_any get_cell_description(cell_gid_type i) const override;
 
-    cell_kind get_cell_kind(cell_gid_type i) const override {
-        return tiled_recipe_->get_cell_kind(i % tiled_recipe_->num_cells());
-    }
+    cell_kind get_cell_kind(cell_gid_type i) const override;
 
-    cell_size_type num_sources(cell_gid_type i) const override {
-        return tiled_recipe_->num_sources(i % tiled_recipe_->num_cells());
-    }
+    cell_size_type num_sources(cell_gid_type i) const override;
 
-    cell_size_type num_targets(cell_gid_type i) const override {
-        return tiled_recipe_->num_targets(i % tiled_recipe_->num_cells());
-    }
+    cell_size_type num_targets(cell_gid_type i) const override;
 
-    // Only function that calls the underlying tile's function on the same gid.
-    // This is because applying transformations to event generators is not straightforward.
-    std::vector<event_generator> event_generators(cell_gid_type i) const override {
-        return tiled_recipe_->event_generators(i);
-    }
+    std::vector<event_generator> event_generators(cell_gid_type i) const override;
 
-    // Take connections_on from the original tile recipe for the cell we are duplicating.
-    // Transate the source and destination gids
-    std::vector<cell_connection> connections_on(cell_gid_type i) const override {
-        int n_local = tiled_recipe_->num_cells();
-        int n_global = num_cells();
-        int offset = (i / n_local) * n_local;
+    std::vector<cell_connection> connections_on(cell_gid_type i) const override;
 
-        std::vector<cell_connection> conns = tiled_recipe_->connections_on(i % n_local);
+    std::vector<probe_info> get_probes(cell_gid_type i) const override;
 
-        for (unsigned j = 0; j < conns.size(); j++) {
-            conns[j].source.gid = (conns[j].source.gid + offset) % n_global;
-            conns[j].dest.gid = (conns[j].dest.gid + offset) % n_global;
-        }
-        return conns;
-    }
-
-    std::vector<probe_info> get_probes(cell_gid_type i) const override {
-        i %= tiled_recipe_->num_cells();
-        return tiled_recipe_->get_probes(i);
-    }
-
-    std::any get_global_properties(cell_kind ck) const override {
-        return tiled_recipe_->get_global_properties(ck);
-    };
+    std::any get_global_properties(cell_kind ck) const override;
 
     std::unique_ptr<tile> tiled_recipe_;
 };
diff --git a/arbor/symmetric_recipe.cpp b/arbor/symmetric_recipe.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..66b846f81e5aca781e336248ec9a95d2fd15e712
--- /dev/null
+++ b/arbor/symmetric_recipe.cpp
@@ -0,0 +1,56 @@
+#include <arbor/symmetric_recipe.hpp>
+
+namespace arb {
+
+cell_size_type symmetric_recipe::num_cells() const {
+    return tiled_recipe_->num_cells() * tiled_recipe_->num_tiles();
+}
+
+util::unique_any symmetric_recipe::get_cell_description(cell_gid_type i) const {
+    return tiled_recipe_->get_cell_description(i % tiled_recipe_->num_cells());
+}
+
+cell_kind symmetric_recipe::get_cell_kind(cell_gid_type i) const {
+    return tiled_recipe_->get_cell_kind(i % tiled_recipe_->num_cells());
+}
+
+cell_size_type symmetric_recipe::num_sources(cell_gid_type i) const {
+    return tiled_recipe_->num_sources(i % tiled_recipe_->num_cells());
+}
+
+cell_size_type symmetric_recipe::num_targets(cell_gid_type i) const {
+    return tiled_recipe_->num_targets(i % tiled_recipe_->num_cells());
+}
+
+// Only function that calls the underlying tile's function on the same gid.
+// This is because applying transformations to event generators is not straightforward.
+std::vector<event_generator> symmetric_recipe::event_generators(cell_gid_type i) const {
+    return tiled_recipe_->event_generators(i);
+}
+
+// Take connections_on from the original tile recipe for the cell we are duplicating.
+// Transate the source and destination gids
+std::vector<cell_connection> symmetric_recipe::connections_on(cell_gid_type i) const {
+    int n_local = tiled_recipe_->num_cells();
+    int n_global = num_cells();
+    int offset = (i / n_local) * n_local;
+
+    std::vector<cell_connection> conns = tiled_recipe_->connections_on(i % n_local);
+
+    for (unsigned j = 0; j < conns.size(); j++) {
+        conns[j].source.gid = (conns[j].source.gid + offset) % n_global;
+        conns[j].dest.gid = (conns[j].dest.gid + offset) % n_global;
+    }
+    return conns;
+}
+
+std::vector<probe_info> symmetric_recipe::get_probes(cell_gid_type i) const {
+    i %= tiled_recipe_->num_cells();
+    return tiled_recipe_->get_probes(i);
+}
+
+std::any symmetric_recipe::get_global_properties(cell_kind ck) const {
+    return tiled_recipe_->get_global_properties(ck);
+};
+
+} //namespace arb
\ No newline at end of file