diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17d03c9f..f4b48cb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 
 ## [Unreleased]
 ### Changed
+- **API BREAKING CHANGE**: API of filters have been changed to be more correct, explicit and flexible.
+  [#21](https://github.com/tzaeschke/phtree-cpp/issues/21)
+  - Correctness: Converters and distance functions are not copied unnecessarily anymore.
+  - Explicit: 
+    Filters *must* have a mandatory parameter for a converter reference. This ensures that the correct
+            converter is used, probably `tree.converter()`.
+  - Flexible: 
+    Distance functions can be provided through a universal reference (forwarding reference).
+    Also, filters are now movable and copyable. 
+  
 - **API BREAKING CHANGE**: Allow filtering on buckets in multimaps. Multimap filters have different functions
   and function signatures than normal `PhTree` filters. [#26](https://github.com/tzaeschke/phtree-cpp/issues/26)
 
diff --git a/phtree/common/filter.h b/phtree/common/filter.h
index 24abcd7d..a883e0e9 100644
--- a/phtree/common/filter.h
+++ b/phtree/common/filter.h
@@ -105,19 +105,16 @@ struct FilterNoOp {
  * The AABB filter can be used to query a point tree for an axis aligned bounding box (AABB).
  * The result is equivalent to that of the 'begin_query(...)' function.
  */
-template <typename CONVERTER = ConverterIEEE<3>>
+template <typename CONVERTER>
 class FilterAABB {
     using KeyExternal = typename CONVERTER::KeyExternal;
     using KeyInternal = typename CONVERTER::KeyInternal;
     using ScalarInternal = typename CONVERTER::ScalarInternal;
-
     static constexpr auto DIM = CONVERTER::DimInternal;
 
   public:
     FilterAABB(
-        const KeyExternal& min_include,
-        const KeyExternal& max_include,
-        CONVERTER converter = CONVERTER())
+        const KeyExternal& min_include, const KeyExternal& max_include, const CONVERTER& converter)
     : min_external_{min_include}
     , max_external_{max_include}
     , min_internal_{converter.pre(min_include)}
@@ -130,13 +127,13 @@ class FilterAABB {
     void set(const KeyExternal& min_include, const KeyExternal& max_include) {
         min_external_ = min_include;
         max_external_ = max_include;
-        min_internal_ = converter_.pre(min_include);
-        max_internal_ = converter_.pre(max_include);
+        min_internal_ = converter_.get().pre(min_include);
+        max_internal_ = converter_.get().pre(max_include);
     }
 
     template <typename T>
     [[nodiscard]] bool IsEntryValid(const KeyInternal& key, const T& /*value*/) const {
-        auto point = converter_.post(key);
+        auto point = converter_.get().post(key);
         for (dimension_t i = 0; i < DIM; ++i) {
             if (point[i] < min_external_[i] || point[i] > max_external_[i]) {
                 return false;
@@ -163,42 +160,39 @@ class FilterAABB {
     }
 
   private:
-    const KeyExternal min_external_;
-    const KeyExternal max_external_;
-    const KeyInternal min_internal_;
-    const KeyInternal max_internal_;
-    const CONVERTER converter_;
+    KeyExternal min_external_;
+    KeyExternal max_external_;
+    KeyInternal min_internal_;
+    KeyInternal max_internal_;
+    std::reference_wrapper<const CONVERTER> converter_;
 };
 
 /*
  * The sphere filter can be used to query a point tree for a sphere.
  */
-template <
-    typename CONVERTER = ConverterIEEE<3>,
-    typename DISTANCE = DistanceEuclidean<CONVERTER::DimInternal>>
+template <typename CONVERTER, typename DISTANCE>
 class FilterSphere {
     using KeyExternal = typename CONVERTER::KeyExternal;
     using KeyInternal = typename CONVERTER::KeyInternal;
     using ScalarInternal = typename CONVERTER::ScalarInternal;
-    using ScalarExternal = typename CONVERTER::ScalarExternal;
-
     static constexpr auto DIM = CONVERTER::DimInternal;
 
   public:
+    template <typename DIST = DistanceEuclidean<CONVERTER::DimExternal>>
     FilterSphere(
         const KeyExternal& center,
-        const ScalarExternal& radius,
-        CONVERTER converter = CONVERTER(),
-        DISTANCE distance_function = DISTANCE())
+        const double radius,
+        const CONVERTER& converter,
+        DIST&& distance_function = DIST())
     : center_external_{center}
     , center_internal_{converter.pre(center)}
     , radius_{radius}
     , converter_{converter}
-    , distance_function_{distance_function} {};
+    , distance_function_(std::forward<DIST>(distance_function)){};
 
     template <typename T>
     [[nodiscard]] bool IsEntryValid(const KeyInternal& key, const T&) const {
-        KeyExternal point = converter_.post(key);
+        KeyExternal point = converter_.get().post(key);
         return distance_function_(center_external_, point) <= radius_;
     }
 
@@ -226,17 +220,23 @@ class FilterSphere {
             closest_in_bounds[i] = std::clamp(center_internal_[i], lo, hi);
         }
 
-        KeyExternal closest_point = converter_.post(closest_in_bounds);
+        KeyExternal closest_point = converter_.get().post(closest_in_bounds);
         return distance_function_(center_external_, closest_point) <= radius_;
     }
 
   private:
-    const KeyExternal center_external_;
-    const KeyInternal center_internal_;
-    const ScalarExternal radius_;
-    const CONVERTER converter_;
-    const DISTANCE distance_function_;
+    KeyExternal center_external_;
+    KeyInternal center_internal_;
+    double radius_;
+    std::reference_wrapper<const CONVERTER> converter_;
+    DISTANCE distance_function_;
 };
+// deduction guide
+template <
+    typename CONV,
+    typename DIST = DistanceEuclidean<CONV::DimExternal>,
+    typename P = typename CONV::KeyExternal>
+FilterSphere(const P&, double, const CONV&, DIST&& fn = DIST()) -> FilterSphere<CONV, DIST>;
 
 /*
  * AABB filter for MultiMaps.
@@ -247,7 +247,7 @@ class FilterMultiMapAABB : public FilterAABB<CONVERTER> {
     using KeyInternal = typename CONVERTER::KeyInternal;
 
   public:
-    FilterMultiMapAABB(const Key& min_include, const Key& max_include, const CONVERTER& converter)
+    FilterMultiMapAABB(const Key& min_include, const Key& max_include, CONVERTER& converter)
     : FilterAABB<CONVERTER>(min_include, max_include, converter){};
 
     template <typename ValueT>
@@ -265,7 +265,7 @@ class FilterMultiMapSphere : public FilterSphere<CONVERTER, DISTANCE> {
     using KeyInternal = typename CONVERTER::KeyInternal;
 
   public:
-    template <typename DIST = DistanceEuclidean<CONVERTER::DimInternal>>
+    template <typename DIST = DistanceEuclidean<CONVERTER::DimExternal>>
     FilterMultiMapSphere(
         const Key& center, double radius, const CONVERTER& converter, DIST&& dist_fn = DIST())
     : FilterSphere<CONVERTER, DIST>(center, radius, converter, std::forward<DIST>(dist_fn)){};
@@ -276,7 +276,7 @@ class FilterMultiMapSphere : public FilterSphere<CONVERTER, DISTANCE> {
     }
 };
 // deduction guide
-template <typename CONV, typename DIST = DistanceEuclidean<CONV::DimInternal>, typename P>
+template <typename CONV, typename DIST = DistanceEuclidean<CONV::DimExternal>, typename P>
 FilterMultiMapSphere(const P&, double, const CONV&, DIST&& fn = DIST())
     -> FilterMultiMapSphere<CONV, DIST>;
 
diff --git a/phtree/common/filter_test.cc b/phtree/common/filter_test.cc
index 41905421..614d4812 100644
--- a/phtree/common/filter_test.cc
+++ b/phtree/common/filter_test.cc
@@ -21,7 +21,8 @@
 using namespace improbable::phtree;
 
 TEST(PhTreeFilterTest, FilterSphereTest) {
-    FilterSphere<ConverterNoOp<2, scalar_64_t>, DistanceEuclidean<2>> filter{{5, 3}, 5};
+    ConverterNoOp<2, scalar_64_t> conv{};
+    FilterSphere filter{{5, 3}, 5, conv, DistanceEuclidean<2>{}};
     // root is always valid
     ASSERT_TRUE(filter.IsNodeValid({0, 0}, 63));
     // valid because node encompasses the circle
@@ -44,8 +45,9 @@ TEST(PhTreeFilterTest, FilterSphereTest) {
     ASSERT_FALSE(filter.IsEntryValid({3, 8}, nullptr));
 }
 
-TEST(PhTreeFilterTest, BoxFilterTest) {
-    FilterAABB<ConverterNoOp<2, scalar_64_t>> filter{{3, 3}, {7, 7}};
+TEST(PhTreeFilterTest, FilterAABBTest) {
+    ConverterNoOp<2, scalar_64_t> conv{};
+    FilterAABB filter{{3, 3}, {7, 7}, conv};
     // root is always valid
     ASSERT_TRUE(filter.IsNodeValid({0, 0}, 63));
     // valid because node encompasses the AABB
@@ -63,4 +65,62 @@ TEST(PhTreeFilterTest, FilterNoOpSmokeTest) {
     auto filter = FilterNoOp();
     ASSERT_TRUE(filter.IsNodeValid<PhPoint<3>>({3, 7, 2}, 10));
     ASSERT_TRUE(filter.IsEntryValid<PhPoint<3>>({3, 7, 2}, 10));
-}
\ No newline at end of file
+}
+
+template <typename FILTER>
+void TestAssignability() {
+    ASSERT_TRUE(std::is_copy_constructible_v<FILTER>);
+    ASSERT_TRUE(std::is_copy_assignable_v<FILTER>);
+    ASSERT_TRUE(std::is_move_constructible_v<FILTER>);
+    ASSERT_TRUE(std::is_move_assignable_v<FILTER>);
+}
+
+TEST(PhTreeFilterTest, FilterAssignableTest) {
+    using CONV = ConverterIEEE<3>;
+    using DIST = DistanceEuclidean<3>;
+    TestAssignability<FilterNoOp>();
+    TestAssignability<FilterAABB<CONV>>();
+    TestAssignability<FilterSphere<CONV, DIST>>();
+    TestAssignability<FilterMultiMapAABB<CONV>>();
+    TestAssignability<FilterMultiMapSphere<CONV, DIST>>();
+}
+
+TEST(PhTreeFilterTest, ConverterAssignableTest) {
+    TestAssignability<ConverterIEEE<3>>();
+    TestAssignability<ScalarConverterIEEE>();
+}
+
+class TestConverter : public ConverterMultiply<2, 1, 1> {
+  public:
+    TestConverter() = default;
+
+    TestConverter(const TestConverter&) = delete;
+    TestConverter(TestConverter&&) = delete;
+    TestConverter& operator=(const TestConverter&) = delete;
+    TestConverter& operator=(TestConverter&&) = delete;
+};
+
+TEST(PhTreeFilterTest, ConstructFilterAABBTest) {
+    TestConverter conv;
+    FilterAABB filter1{{3, 3}, {7, 7}, conv};
+    ASSERT_TRUE(filter1.IsNodeValid({0, 0}, 63));
+
+    FilterAABB filter2{{3, 3}, {7, 7}, TestConverter()};
+    ASSERT_TRUE(filter2.IsNodeValid({0, 0}, 63));
+}
+
+TEST(PhTreeFilterTest, ConstructFilterSphereTest) {
+    DistanceL1<2> dist;
+    TestConverter conv;
+    FilterSphere filter1a{{3, 3}, 7, conv};
+    ASSERT_TRUE(filter1a.IsNodeValid({0, 0}, 63));
+    FilterSphere filter1b{{3, 3}, 7, conv, {}};
+    ASSERT_TRUE(filter1b.IsNodeValid({0, 0}, 63));
+    FilterSphere filter1c{{3, 3}, 7, conv, dist};
+    ASSERT_TRUE(filter1c.IsNodeValid({0, 0}, 63));
+    FilterSphere filter1d{{3, 3}, 7, conv, DistanceL1<2>{}};
+    ASSERT_TRUE(filter1d.IsNodeValid({0, 0}, 63));
+
+    FilterSphere filter2{{3, 3}, 7, TestConverter()};
+    ASSERT_TRUE(filter2.IsNodeValid({0, 0}, 63));
+}
diff --git a/phtree/phtree.h b/phtree/phtree.h
index 11087c73..44194f7c 100644
--- a/phtree/phtree.h
+++ b/phtree/phtree.h
@@ -40,10 +40,11 @@ class PhTree {
         typename std::conditional<(DIM == DimInternal), QueryPoint, QueryIntersect>::type;
 
   public:
+    // Unless specified otherwise this is just PhBox<DIM, SCALAR_EXTERNAL>
     using QueryBox = typename CONVERTER::QueryBoxExternal;
 
-    template <typename CONVERTER2 = CONVERTER>
-    explicit PhTree(CONVERTER2&& converter = CONVERTER())
+    template <typename CONV = CONVERTER>
+    explicit PhTree(CONV&& converter = CONV())
     : tree_{&converter_}, converter_{converter} {}
 
     PhTree(const PhTree& other) = delete;
diff --git a/phtree/phtree_multimap_d_test_filter.cc b/phtree/phtree_multimap_d_test_filter.cc
index 7d89863d..4aa53ab5 100644
--- a/phtree/phtree_multimap_d_test_filter.cc
+++ b/phtree/phtree_multimap_d_test_filter.cc
@@ -491,6 +491,33 @@ void referenceSphereQuery(
     }
 }
 
+template <dimension_t DIM>
+void referenceAABBQuery(
+    std::vector<TestPoint<DIM>>& points,
+    TestPoint<DIM>& center,
+    double radius,
+    std::set<size_t>& result) {
+    for (size_t i = 0; i < points.size(); i++) {
+        auto& p = points[i];
+        bool inside = true;
+        for (dimension_t i = 0; i < DIM; ++i) {
+            inside &= std::abs(p[i] - center[i]) <= radius;
+        }
+
+        if (inside) {
+            result.insert(i);
+        }
+    }
+}
+
+template <dimension_t DIM>
+PhBoxD<DIM> QueryBox(PhPointD<DIM>& center, double radius) {
+    typename TestTree<DIM, size_t>::QueryBox query_box{
+        {center[0] - radius, center[1] - radius, center[2] - radius},
+        {center[0] + radius, center[1] + radius, center[2] + radius}};
+    return query_box;
+}
+
 // We use 'int&' because gtest does not compile with assertions in non-void functions.
 template <dimension_t DIM>
 void testSphereQuery(TestPoint<DIM>& center, double radius, size_t N, int& result) {
@@ -512,33 +539,27 @@ void testSphereQuery(TestPoint<DIM>& center, double radius, size_t N, int& resul
     ASSERT_EQ(referenceResult.size(), result);
 }
 
-TEST(PhTreeMMDFilterTest, TestSphereQuery0) {
-    const dimension_t dim = 3;
-    TestPoint<dim> p{-10000, -10000, -10000};
-    int n = 0;
-    testSphereQuery<dim>(p, 0.1, 100, n);
-    ASSERT_EQ(0, n);
-}
+template <dimension_t DIM>
+void testSphereQueryWithBox(TestPoint<DIM>& center, double radius, size_t N, int& result) {
+    TestTree<DIM, size_t> tree;
+    std::vector<TestPoint<DIM>> points;
+    populate(tree, points, N);
 
-TEST(PhTreeMMDFilterTest, TestSphereQueryMany) {
-    const dimension_t dim = 3;
-    TestPoint<dim> p{0, 0, 0};
-    int n = 0;
-    testSphereQuery<dim>(p, 1000, 1000, n);
-    ASSERT_GT(n, 400);
-    ASSERT_LT(n, 800);
-}
+    std::set<size_t> referenceResult;
+    referenceSphereQuery(points, center, radius, referenceResult);
 
-TEST(PhTreeMMDFilterTest, TestSphereQueryAll) {
-    const dimension_t dim = 3;
-    TestPoint<dim> p{0, 0, 0};
-    int n = 0;
-    testSphereQuery<dim>(p, 10000, 1000, n);
-    ASSERT_EQ(1000, n);
+    result = 0;
+    auto query_box = QueryBox(center, radius);
+    auto filter = FilterMultiMapSphere(center, radius, tree.converter());
+    for (auto it = tree.begin_query(query_box, filter); it != tree.end(); it++) {
+        auto& x = *it;
+        ASSERT_GE(x, 0);
+        ASSERT_EQ(referenceResult.count(x), 1);
+        result++;
+    }
+    ASSERT_EQ(referenceResult.size(), result);
 }
 
-
-// We use 'int&' because gtest does not compile with assertions in non-void functions.
 template <dimension_t DIM>
 void testSphereQueryForEach(TestPoint<DIM>& center, double radius, size_t N, int& result) {
     TestTree<DIM, size_t> tree;
@@ -556,36 +577,109 @@ void testSphereQueryForEach(TestPoint<DIM>& center, double radius, size_t N, int
         ++result;
     };
     tree.for_each(callback, filter);
-//    for (auto it = tree.begin(filter); it != tree.end(); it++) {
-//        auto& x = *it;
-//        ASSERT_GE(x, 0);
-//        ASSERT_EQ(referenceResult.count(x), 1);
-//        result++;
-//    }
     ASSERT_EQ(referenceResult.size(), result);
 }
+template <dimension_t DIM>
+void testSphereQueryForEachQueryBox(TestPoint<DIM>& center, double radius, size_t N, int& result) {
+    TestTree<DIM, size_t> tree;
+    std::vector<TestPoint<DIM>> points;
+    populate(tree, points, N);
+
+    std::set<size_t> referenceResult;
+    referenceSphereQuery(points, center, radius, referenceResult);
 
-TEST(PhTreeMMDFilterTest, TestSphereQueryForEach0) {
-    const dimension_t dim = 3;
-    TestPoint<dim> p{-10000, -10000, -10000};
+    result = 0;
+    auto query_box = QueryBox(center, radius);
+    auto filter = FilterMultiMapSphere(center, radius, tree.converter());
+    auto callback = [&result, &referenceResult](PhPointD<DIM>, const size_t& x) {
+        ASSERT_GE(x, 0);
+        ASSERT_EQ(referenceResult.count(x), 1);
+        ++result;
+    };
+    tree.for_each(query_box, callback, filter);
+    ASSERT_EQ(referenceResult.size(), result);
+}
+
+template <dimension_t DIM>
+void testAABBQuery(TestPoint<DIM>& center, double radius, size_t N, int& result) {
+    TestTree<DIM, size_t> tree;
+    std::vector<TestPoint<DIM>> points;
+    populate(tree, points, N);
+
+    std::set<size_t> referenceResult;
+    referenceAABBQuery(points, center, radius, referenceResult);
+
+    result = 0;
+    auto query_box = QueryBox(center, radius);
+    auto filter = FilterMultiMapAABB(query_box.min(), query_box.max(), tree.converter());
+    for (auto it = tree.begin(filter); it != tree.end(); it++) {
+        auto& x = *it;
+        ASSERT_GE(x, 0);
+        ASSERT_EQ(referenceResult.count(x), 1);
+        result++;
+    }
+    ASSERT_EQ(referenceResult.size(), result);
+}
+
+template <dimension_t DIM, typename QUERY>
+void Query0(QUERY query) {
+    TestPoint<DIM> p{-10000, -10000, -10000};
     int n = 0;
-    testSphereQueryForEach<dim>(p, 0.1, 100, n);
+    query(p, 0.1, 100, n);
     ASSERT_EQ(0, n);
 }
 
-TEST(PhTreeMMDFilterTest, TestSphereQueryForEachMany) {
-    const dimension_t dim = 3;
-    TestPoint<dim> p{0, 0, 0};
+template <dimension_t DIM, typename QUERY>
+void QueryMany(QUERY query) {
+    TestPoint<DIM> p{0, 0, 0};
     int n = 0;
-    testSphereQueryForEach<dim>(p, 1000, 1000, n);
+    query(p, 1000, 1000, n);
     ASSERT_GT(n, 400);
     ASSERT_LT(n, 800);
 }
 
-TEST(PhTreeMMDFilterTest, TestSphereQueryForEachAll) {
-    const dimension_t dim = 3;
-    TestPoint<dim> p{0, 0, 0};
+template <dimension_t DIM, typename QUERY>
+void QueryManyAABB(QUERY query) {
+    TestPoint<DIM> p{0, 0, 0};
+    int n = 0;
+    query(p, 1000, 1000, n);
+    ASSERT_EQ(n, 1000);
+}
+
+template <dimension_t DIM, typename QUERY>
+void QueryAll(QUERY query) {
+    TestPoint<DIM> p{0, 0, 0};
     int n = 0;
-    testSphereQueryForEach<dim>(p, 10000, 1000, n);
+    query(p, 10000, 1000, n);
     ASSERT_EQ(1000, n);
-}
\ No newline at end of file
+}
+
+TEST(PhTreeMMDFilterTest, TestSphereQuery) {
+    Query0<3>(&testSphereQuery<3>);
+    QueryMany<3>(&testSphereQuery<3>);
+    QueryAll<3>(&testSphereQuery<3>);
+}
+
+TEST(PhTreeMMDFilterTest, TestSphereQueryWithQueryBox) {
+    Query0<3>(&testSphereQueryWithBox<3>);
+    QueryMany<3>(&testSphereQueryWithBox<3>);
+    QueryAll<3>(&testSphereQueryWithBox<3>);
+}
+
+TEST(PhTreeMMDFilterTest, TestSphereQueryForEach) {
+    Query0<3>(&testSphereQueryForEach<3>);
+    QueryMany<3>(&testSphereQueryForEach<3>);
+    QueryAll<3>(&testSphereQueryForEach<3>);
+}
+
+TEST(PhTreeMMDFilterTest, TestSphereQueryForEachWithQueryBox) {
+    Query0<3>(&testSphereQueryForEachQueryBox<3>);
+    QueryMany<3>(&testSphereQueryForEachQueryBox<3>);
+    QueryAll<3>(&testSphereQueryForEachQueryBox<3>);
+}
+
+TEST(PhTreeMMDFilterTest, TestAABBQuery) {
+    Query0<3>(&testAABBQuery<3>);
+    QueryManyAABB<3>(&testAABBQuery<3>);
+    QueryAll<3>(&testAABBQuery<3>);
+}
