propagate_const
authorYedidya Feldblum <yfeldblum@fb.com>
Wed, 20 Dec 2017 02:57:03 +0000 (18:57 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 20 Dec 2017 03:05:10 +0000 (19:05 -0800)
Summary: [Folly] `propagate_const`, backported from C++ Library Fundamentals TS v2.

Reviewed By: ericniebler

Differential Revision: D6589681

fbshipit-source-id: cdc8981d17938b99afe60e2baefff7deb5316612

folly/lang/PropagateConst.h [new file with mode: 0644]
folly/lang/test/PropagateConstTest.cpp [new file with mode: 0644]

diff --git a/folly/lang/PropagateConst.h b/folly/lang/PropagateConst.h
new file mode 100644 (file)
index 0000000..5b359dc
--- /dev/null
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2017-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+namespace folly {
+
+template <typename Pointer>
+class propagate_const;
+
+template <typename Pointer>
+constexpr Pointer& get_underlying(propagate_const<Pointer>& obj) {
+  return obj.pointer_;
+}
+
+template <typename Pointer>
+constexpr Pointer const& get_underlying(propagate_const<Pointer> const& obj) {
+  return obj.pointer_;
+}
+
+namespace detail {
+template <typename>
+struct is_propagate_const : std::false_type {};
+template <typename Pointer>
+struct is_propagate_const<propagate_const<Pointer>> : std::true_type {};
+template <typename T>
+using is_decay_propagate_const = is_propagate_const<_t<std::decay<T>>>;
+
+namespace propagate_const_adl {
+using std::swap;
+template <typename T>
+auto adl_swap(T& a, T& b) noexcept(noexcept(swap(a, b)))
+    -> decltype(swap(a, b)) {
+  swap(a, b);
+}
+} // namespace propagate_const_adl
+} // namespace detail
+
+//  mimic: std::experimental::propagate_const, C++ Library Fundamentals TS v2
+template <typename Pointer>
+class propagate_const {
+ public:
+  using element_type =
+      _t<std::remove_reference<decltype(*std::declval<Pointer&>())>>;
+
+  constexpr propagate_const() = default;
+  constexpr propagate_const(propagate_const&&) = default;
+  constexpr propagate_const(propagate_const const&) = delete;
+
+  template <
+      typename OtherPointer,
+      _t<std::enable_if<
+          std::is_constructible<Pointer, OtherPointer&&>::value &&
+              !std::is_convertible<OtherPointer&&, Pointer>::value,
+          int>> = 0>
+  constexpr explicit propagate_const(propagate_const<OtherPointer>&& other)
+      : pointer_(static_cast<OtherPointer&&>(other.pointer_)) {}
+
+  template <
+      typename OtherPointer,
+      _t<std::enable_if<
+          std::is_constructible<Pointer, OtherPointer&&>::value &&
+              std::is_convertible<OtherPointer&&, Pointer>::value,
+          int>> = 0>
+  constexpr propagate_const(propagate_const<OtherPointer>&& other)
+      : pointer_(static_cast<OtherPointer&&>(other.pointer_)) {}
+
+  template <
+      typename OtherPointer,
+      _t<std::enable_if<
+          !detail::is_decay_propagate_const<OtherPointer>::value &&
+              std::is_constructible<Pointer, OtherPointer&&>::value &&
+              !std::is_convertible<OtherPointer&&, Pointer>::value,
+          int>> = 0>
+  constexpr explicit propagate_const(OtherPointer&& other)
+      : pointer_(static_cast<OtherPointer&&>(other)) {}
+
+  template <
+      typename OtherPointer,
+      _t<std::enable_if<
+          !detail::is_decay_propagate_const<OtherPointer>::value &&
+              std::is_constructible<Pointer, OtherPointer&&>::value &&
+              std::is_convertible<OtherPointer&&, Pointer>::value,
+          int>> = 0>
+  constexpr propagate_const(OtherPointer&& other)
+      : pointer_(static_cast<OtherPointer&&>(other)) {}
+
+  constexpr propagate_const& operator=(propagate_const&&) = default;
+  constexpr propagate_const& operator=(propagate_const const&) = delete;
+
+  template <
+      typename OtherPointer,
+      typename = _t<
+          std::enable_if<std::is_convertible<OtherPointer&&, Pointer>::value>>>
+  FOLLY_CPP14_CONSTEXPR propagate_const& operator=(
+      propagate_const<OtherPointer>&& other) {
+    pointer_ = static_cast<OtherPointer&&>(other.pointer_);
+  }
+
+  template <
+      typename OtherPointer,
+      typename = _t<std::enable_if<
+          !detail::is_decay_propagate_const<OtherPointer>::value &&
+          std::is_convertible<OtherPointer&&, Pointer>::value>>>
+  FOLLY_CPP14_CONSTEXPR propagate_const& operator=(OtherPointer&& other) {
+    pointer_ = static_cast<OtherPointer&&>(other);
+  }
+
+  FOLLY_CPP14_CONSTEXPR void swap(propagate_const& other) noexcept(noexcept(
+      detail::propagate_const_adl::adl_swap(pointer_, other.pointer_))) {
+    detail::propagate_const_adl::adl_swap(pointer_, other.pointer_);
+  }
+
+  constexpr element_type* get() {
+    return get_(pointer_);
+  }
+
+  constexpr element_type const* get() const {
+    return get_(pointer_);
+  }
+
+  constexpr explicit operator bool() const {
+    return static_cast<bool>(pointer_);
+  }
+
+  constexpr element_type& operator*() {
+    return *get();
+  }
+
+  constexpr element_type const& operator*() const {
+    return *get();
+  }
+
+  constexpr element_type* operator->() {
+    return get();
+  }
+
+  constexpr element_type const* operator->() const {
+    return get();
+  }
+
+  template <
+      typename OtherPointer = Pointer,
+      typename = _t<std::enable_if<
+          std::is_pointer<OtherPointer>::value ||
+          std::is_convertible<OtherPointer, element_type*>::value>>>
+  constexpr operator element_type*() {
+    return get();
+  }
+
+  template <
+      typename OtherPointer = Pointer,
+      typename = _t<std::enable_if<
+          std::is_pointer<OtherPointer>::value ||
+          std::is_convertible<OtherPointer, element_type const*>::value>>>
+  constexpr operator element_type const*() const {
+    return get();
+  }
+
+ private:
+  friend Pointer& get_underlying<>(propagate_const&);
+  friend Pointer const& get_underlying<>(propagate_const const&);
+
+  template <typename T>
+  static T* get_(T* t) {
+    return t;
+  }
+  template <typename T>
+  static auto get_(T& t) -> decltype(t.get()) {
+    return t.get();
+  }
+
+  Pointer pointer_;
+};
+
+template <typename Pointer>
+FOLLY_CPP14_CONSTEXPR void swap(
+    propagate_const<Pointer>& a,
+    propagate_const<Pointer>& b) noexcept(noexcept(a.swap(b))) {
+  a.swap(b);
+}
+
+template <typename Pointer>
+constexpr bool operator==(propagate_const<Pointer> const& a, std::nullptr_t) {
+  return get_underlying(a) == nullptr;
+}
+
+template <typename Pointer>
+constexpr bool operator==(std::nullptr_t, propagate_const<Pointer> const& a) {
+  return nullptr == get_underlying(a);
+}
+
+template <typename Pointer>
+constexpr bool operator!=(propagate_const<Pointer> const& a, std::nullptr_t) {
+  return get_underlying(a) != nullptr;
+}
+
+template <typename Pointer>
+constexpr bool operator!=(std::nullptr_t, propagate_const<Pointer> const& a) {
+  return nullptr != get_underlying(a);
+}
+
+template <typename Pointer>
+constexpr bool operator==(
+    propagate_const<Pointer> const& a,
+    propagate_const<Pointer> const& b) {
+  return get_underlying(a) == get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator!=(
+    propagate_const<Pointer> const& a,
+    propagate_const<Pointer> const& b) {
+  return get_underlying(a) != get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator<(
+    propagate_const<Pointer> const& a,
+    propagate_const<Pointer> const& b) {
+  return get_underlying(a) < get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator<=(
+    propagate_const<Pointer> const& a,
+    propagate_const<Pointer> const& b) {
+  return get_underlying(a) <= get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator>(
+    propagate_const<Pointer> const& a,
+    propagate_const<Pointer> const& b) {
+  return get_underlying(a) > get_underlying(b);
+}
+
+template <typename Pointer>
+constexpr bool operator>=(
+    propagate_const<Pointer> const& a,
+    propagate_const<Pointer> const& b) {
+  return get_underlying(a) >= get_underlying(b);
+}
+
+//  Note: contrary to the specification, the heterogeneous comparison operators
+//  only participate in overload resolution when the equivalent heterogeneous
+//  comparison operators on the underlying pointers, as returned by invocation
+//  of get_underlying, would also participate in overload resolution.
+
+template <typename Pointer, typename Other>
+constexpr auto operator==(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) == b, false) {
+  return get_underlying(a) == b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator!=(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) != b, false) {
+  return get_underlying(a) != b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator<(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) < b, false) {
+  return get_underlying(a) < b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator<=(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) <= b, false) {
+  return get_underlying(a) <= b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator>(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) > b, false) {
+  return get_underlying(a) > b;
+}
+
+template <typename Pointer, typename Other>
+constexpr auto operator>=(propagate_const<Pointer> const& a, Other const& b)
+    -> decltype(get_underlying(a) >= b, false) {
+  return get_underlying(a) >= b;
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator==(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a == get_underlying(b), false) {
+  return a == get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator!=(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a != get_underlying(b), false) {
+  return a != get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator<(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a < get_underlying(b), false) {
+  return a < get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator<=(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a <= get_underlying(b), false) {
+  return a <= get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator>(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a > get_underlying(b), false) {
+  return a > get_underlying(b);
+}
+
+template <typename Other, typename Pointer>
+constexpr auto operator>=(Other const& a, propagate_const<Pointer> const& b)
+    -> decltype(a >= get_underlying(b), false) {
+  return a >= get_underlying(b);
+}
+
+} // namespace folly
+
+namespace std {
+
+template <typename Pointer>
+struct hash<folly::propagate_const<Pointer>> : private hash<Pointer> {
+  using hash<Pointer>::hash;
+
+  size_t operator()(folly::propagate_const<Pointer> const& obj) const {
+    return hash<Pointer>::operator()(folly::get_underlying(obj));
+  }
+};
+
+template <typename Pointer>
+struct equal_to<folly::propagate_const<Pointer>> : private equal_to<Pointer> {
+  using equal_to<Pointer>::equal_to;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return equal_to<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct not_equal_to<folly::propagate_const<Pointer>>
+    : private not_equal_to<Pointer> {
+  using not_equal_to<Pointer>::not_equal_to;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return not_equal_to<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct less<folly::propagate_const<Pointer>> : private less<Pointer> {
+  using less<Pointer>::less;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return less<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct greater<folly::propagate_const<Pointer>> : private greater<Pointer> {
+  using greater<Pointer>::greater;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return greater<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct less_equal<folly::propagate_const<Pointer>>
+    : private less_equal<Pointer> {
+  using less_equal<Pointer>::less_equal;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return less_equal<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+template <typename Pointer>
+struct greater_equal<folly::propagate_const<Pointer>>
+    : private greater_equal<Pointer> {
+  using greater_equal<Pointer>::greater_equal;
+
+  constexpr bool operator()(
+      folly::propagate_const<Pointer> const& a,
+      folly::propagate_const<Pointer> const& b) {
+    return greater_equal<Pointer>::operator()(
+        folly::get_underlying(a), folly::get_underlying(b));
+  }
+};
+
+} // namespace std
diff --git a/folly/lang/test/PropagateConstTest.cpp b/folly/lang/test/PropagateConstTest.cpp
new file mode 100644 (file)
index 0000000..f68c28f
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2017-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/lang/PropagateConst.h>
+
+#include <memory>
+
+#include <folly/portability/GTest.h>
+
+using namespace folly;
+
+class PropagateConstTest : public testing::Test {};
+
+//  force complete template instantiations
+template class folly::propagate_const<int*>;
+template class folly::propagate_const<std::unique_ptr<int>>;
+template class folly::propagate_const<std::shared_ptr<int>>;
+
+template <typename T>
+static bool is_const(T&&) {
+  return std::is_const<_t<std::remove_reference<T>>>::value;
+}
+
+template <typename T>
+using pc = propagate_const<T>;
+
+TEST_F(PropagateConstTest, construct_assign) {
+  struct Source {
+    int& operator*();
+    int* get();
+  };
+  struct Implicit {
+    int& operator*();
+    int* get();
+    /* implicit */ Implicit(Source) {}
+  };
+  struct Explicit {
+    int& operator*();
+    int* get();
+    explicit Explicit(Source) {}
+  };
+
+  EXPECT_TRUE((std::is_constructible<pc<Implicit>, Source>::value));
+  EXPECT_TRUE((std::is_constructible<pc<Explicit>, Source>::value));
+  EXPECT_TRUE((std::is_convertible<Source, pc<Implicit>>::value));
+  EXPECT_FALSE((std::is_convertible<Source, pc<Explicit>>::value));
+  EXPECT_TRUE((std::is_assignable<pc<Implicit>, Source>::value));
+  EXPECT_FALSE((std::is_assignable<pc<Explicit>, Source>::value));
+
+  EXPECT_TRUE((std::is_constructible<pc<Implicit>, pc<Source>>::value));
+  EXPECT_TRUE((std::is_constructible<pc<Explicit>, pc<Source>>::value));
+  EXPECT_TRUE((std::is_convertible<pc<Source>, pc<Implicit>>::value));
+  EXPECT_FALSE((std::is_convertible<pc<Source>, pc<Explicit>>::value));
+  EXPECT_TRUE((std::is_assignable<pc<Implicit>, pc<Source>>::value));
+  EXPECT_FALSE((std::is_assignable<pc<Explicit>, pc<Source>>::value));
+}
+
+TEST_F(PropagateConstTest, get) {
+  int a = 3;
+  auto pc_a = pc<int*>(&a);
+
+  EXPECT_EQ(&a, pc_a.get());
+  EXPECT_EQ(&a, as_const(pc_a).get());
+  EXPECT_FALSE(is_const(*pc_a.get()));
+  EXPECT_TRUE(is_const(*as_const(pc_a).get()));
+}
+
+TEST_F(PropagateConstTest, op_indirect) {
+  int a = 3;
+  auto pc_a = pc<int*>(&a);
+
+  EXPECT_EQ(&a, &*pc_a);
+  EXPECT_EQ(&a, &*as_const(pc_a));
+  EXPECT_FALSE(is_const(*pc_a));
+  EXPECT_TRUE(is_const(*as_const(pc_a)));
+}
+
+TEST_F(PropagateConstTest, op_element_type_ptr) {
+  int a = 3;
+  auto pc_a = pc<int*>(&a);
+
+  EXPECT_EQ(&a, static_cast<int*>(pc_a));
+  EXPECT_EQ(&a, static_cast<int const*>(as_const(pc_a)));
+}
+
+TEST_F(PropagateConstTest, op_bool) {
+  int a = 3;
+  auto pc_a = pc<int*>(&a);
+  auto pc_0 = pc<int*>(nullptr);
+
+  EXPECT_TRUE(pc_a);
+  EXPECT_FALSE(pc_0);
+}
+
+TEST_F(PropagateConstTest, get_underlying) {
+  int a = 3;
+  auto pc_a = pc<int*>(&a);
+
+  EXPECT_EQ(&a, get_underlying(pc_a));
+  EXPECT_EQ(&a, get_underlying(as_const(pc_a)));
+  EXPECT_FALSE(is_const(get_underlying(pc_a)));
+  EXPECT_TRUE(is_const(get_underlying(as_const(pc_a))));
+  EXPECT_TRUE(&get_underlying(pc_a) == &get_underlying(as_const(pc_a)));
+}
+
+TEST_F(PropagateConstTest, swap) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  swap(pc_a, pc_b);
+  EXPECT_EQ(3, *pc_b);
+  EXPECT_EQ(4, *pc_a);
+
+  swap(pc_a, pc_b);
+  EXPECT_EQ(3, *pc_a);
+  EXPECT_EQ(4, *pc_b);
+}
+
+TEST_F(PropagateConstTest, op_equal_to) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = [](auto&& x, auto&& y) { return x == y; };
+  EXPECT_TRUE(_(pc_a, pc_a));
+  EXPECT_FALSE(_(pc_a, pc_b));
+  EXPECT_FALSE(_(pc_a, nullptr));
+  EXPECT_TRUE(_(pc_a, &a));
+  EXPECT_FALSE(_(pc_a, &b));
+  EXPECT_TRUE(_(&a, pc_a));
+  EXPECT_FALSE(_(&b, pc_a));
+}
+
+TEST_F(PropagateConstTest, op_not_equal_to) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = [](auto&& x, auto&& y) { return x != y; };
+  EXPECT_FALSE(_(pc_a, pc_a));
+  EXPECT_TRUE(_(pc_a, pc_b));
+  EXPECT_TRUE(_(pc_a, nullptr));
+  EXPECT_FALSE(_(pc_a, &a));
+  EXPECT_TRUE(_(pc_a, &b));
+  EXPECT_FALSE(_(&a, pc_a));
+  EXPECT_TRUE(_(&b, pc_a));
+}
+
+TEST_F(PropagateConstTest, op_less) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = [](auto&& x, auto&& y) { return x < y; };
+  EXPECT_FALSE(_(pc_a, pc_a));
+  EXPECT_FALSE(_(pc_a, &a));
+  EXPECT_FALSE(_(&a, pc_a));
+  EXPECT_TRUE(_(pc_a, pc_b));
+  EXPECT_TRUE(_(pc_a, &b));
+  EXPECT_TRUE(_(&a, pc_b));
+  EXPECT_FALSE(_(pc_b, pc_a));
+  EXPECT_FALSE(_(pc_b, &a));
+  EXPECT_FALSE(_(&b, pc_a));
+  EXPECT_FALSE(_(pc_b, pc_b));
+  EXPECT_FALSE(_(pc_b, &b));
+  EXPECT_FALSE(_(&b, pc_b));
+}
+
+TEST_F(PropagateConstTest, op_greater) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = [](auto&& x, auto&& y) { return x > y; };
+  EXPECT_FALSE(_(pc_a, pc_a));
+  EXPECT_FALSE(_(pc_a, &a));
+  EXPECT_FALSE(_(&a, pc_a));
+  EXPECT_FALSE(_(pc_a, pc_b));
+  EXPECT_FALSE(_(pc_a, &b));
+  EXPECT_FALSE(_(&a, pc_b));
+  EXPECT_TRUE(_(pc_b, pc_a));
+  EXPECT_TRUE(_(pc_b, &a));
+  EXPECT_TRUE(_(&b, pc_a));
+  EXPECT_FALSE(_(pc_b, pc_b));
+  EXPECT_FALSE(_(pc_b, &b));
+  EXPECT_FALSE(_(&b, pc_b));
+}
+
+TEST_F(PropagateConstTest, op_less_equal) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = [](auto&& x, auto&& y) { return x <= y; };
+  EXPECT_TRUE(_(pc_a, pc_a));
+  EXPECT_TRUE(_(pc_a, &a));
+  EXPECT_TRUE(_(&a, pc_a));
+  EXPECT_TRUE(_(pc_a, pc_b));
+  EXPECT_TRUE(_(pc_a, &b));
+  EXPECT_TRUE(_(&a, pc_b));
+  EXPECT_FALSE(_(pc_b, pc_a));
+  EXPECT_FALSE(_(pc_b, &a));
+  EXPECT_FALSE(_(&b, pc_a));
+  EXPECT_TRUE(_(pc_b, pc_b));
+  EXPECT_TRUE(_(pc_b, &b));
+  EXPECT_TRUE(_(&b, pc_b));
+}
+
+TEST_F(PropagateConstTest, op_greater_equal) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = [](auto&& x, auto&& y) { return x >= y; };
+  EXPECT_TRUE(_(pc_a, pc_a));
+  EXPECT_TRUE(_(pc_a, &a));
+  EXPECT_TRUE(_(&a, pc_a));
+  EXPECT_FALSE(_(pc_a, pc_b));
+  EXPECT_FALSE(_(pc_a, &b));
+  EXPECT_FALSE(_(&a, pc_b));
+  EXPECT_TRUE(_(pc_b, pc_a));
+  EXPECT_TRUE(_(pc_b, &a));
+  EXPECT_TRUE(_(&b, pc_a));
+  EXPECT_TRUE(_(pc_b, pc_b));
+  EXPECT_TRUE(_(pc_b, &b));
+  EXPECT_TRUE(_(&b, pc_b));
+}
+
+TEST_F(PropagateConstTest, hash) {
+  int a = 3;
+  auto pc_a = pc<int*>(&a);
+
+  EXPECT_EQ(std::hash<int*>()(&a), std::hash<pc<int*>>()(pc_a));
+}
+
+TEST_F(PropagateConstTest, equal_to) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = std::equal_to<pc<int*>>{};
+  EXPECT_TRUE(_(pc_a, pc_a));
+  EXPECT_FALSE(_(pc_a, pc_b));
+}
+
+TEST_F(PropagateConstTest, not_equal_to) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = std::not_equal_to<pc<int*>>{};
+  EXPECT_FALSE(_(pc_a, pc_a));
+  EXPECT_TRUE(_(pc_a, pc_b));
+}
+
+TEST_F(PropagateConstTest, less) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = std::less<pc<int*>>{};
+  EXPECT_FALSE(_(pc_a, pc_a));
+  EXPECT_TRUE(_(pc_a, pc_b));
+  EXPECT_FALSE(_(pc_b, pc_a));
+  EXPECT_FALSE(_(pc_b, pc_b));
+}
+
+TEST_F(PropagateConstTest, greater) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = std::greater<pc<int*>>{};
+  EXPECT_FALSE(_(pc_a, pc_a));
+  EXPECT_FALSE(_(pc_a, pc_b));
+  EXPECT_TRUE(_(pc_b, pc_a));
+  EXPECT_FALSE(_(pc_b, pc_b));
+}
+
+TEST_F(PropagateConstTest, less_equal) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = std::less_equal<pc<int*>>{};
+  EXPECT_TRUE(_(pc_a, pc_a));
+  EXPECT_TRUE(_(pc_a, pc_b));
+  EXPECT_FALSE(_(pc_b, pc_a));
+  EXPECT_TRUE(_(pc_b, pc_b));
+}
+
+TEST_F(PropagateConstTest, greater_equal) {
+  int a = 3;
+  int b = 4;
+  auto pc_a = pc<int*>(&a);
+  auto pc_b = pc<int*>(&b);
+
+  auto _ = std::greater_equal<pc<int*>>{};
+  EXPECT_TRUE(_(pc_a, pc_a));
+  EXPECT_FALSE(_(pc_a, pc_b));
+  EXPECT_TRUE(_(pc_b, pc_a));
+  EXPECT_TRUE(_(pc_b, pc_b));
+}