]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
common: Add static_ptr 19079/head
authorAdam C. Emerson <aemerson@redhat.com>
Tue, 21 Nov 2017 19:13:48 +0000 (14:13 -0500)
committerAdam C. Emerson <aemerson@redhat.com>
Tue, 28 Nov 2017 19:29:04 +0000 (14:29 -0500)
Add static_ptr, a pointer-like class that contains its own storage,
avoiding use of the heap.

The full range of *_pointer_cast functions are included.

Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
src/common/static_ptr.h [new file with mode: 0644]
src/test/common/CMakeLists.txt
src/test/common/test_static_ptr.cc [new file with mode: 0644]

diff --git a/src/common/static_ptr.h b/src/common/static_ptr.h
new file mode 100644 (file)
index 0000000..9e4ea10
--- /dev/null
@@ -0,0 +1,442 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+ */
+
+#include "common/backport14.h"
+
+namespace ceph {
+// `static_ptr`
+// ===========
+//
+// It would be really nice if polymorphism didn't require a bunch of
+// mucking about with the heap. So let's build something where we
+// don't have to do that.
+//
+namespace _mem {
+
+// This, an operator function, is one of the canonical ways to do type
+// erasure in C++ so long as all operations can be done with subsets
+// of the same arguments (which is not true for function type erasure)
+// it's a pretty good one.
+enum class op {
+  copy, move, destroy, size
+};
+template<typename T>
+static std::size_t op_fun(op oper, void* p1, void* p2)
+{
+  auto me = static_cast<T*>(p1);
+
+  switch (oper) {
+  case op::copy:
+    // One conspicuous downside is that immovable/uncopyable functions
+    // kill compilation right here, even if nobody ever calls the move
+    // or copy methods. Working around this is a pain, since we'd need
+    // four operator functions and a top-level class to
+    // provide/withhold copy/move operations as appropriate.
+    new (p2) T(*me);
+    break;
+
+  case op::move:
+    new (p2) T(std::move(*me));
+    break;
+
+  case op::destroy:
+    me->~T();
+    break;
+
+  case op::size:
+    return sizeof(T);
+  }
+  return 0;
+}
+}
+// The thing itself!
+//
+// The default value for Size may be wrong in almost all cases. You
+// can change it to your heart's content. The upside is that you'll
+// just get a compile error and you can bump it up.
+//
+// I *recommend* having a size constant in header files (or perhaps a
+// using declaration, e.g.
+// ```
+// using StaticFoo = static_ptr<Foo, sizeof(Blah)>`
+// ```
+// in some header file that can be used multiple places) so that when
+// you create a new derived class with a larger size, you only have to
+// change it in one place.
+//
+template<typename Base, std::size_t Size = sizeof(Base)>
+class static_ptr {
+  template<typename U, std::size_t S>
+  friend class static_ptr;
+
+  // Refuse to be set to anything with whose type we are
+  // incompatible. Also never try to eat anything bigger than you are.
+  //
+  template<typename T, std::size_t S>
+  constexpr static int create_ward() noexcept {
+    static_assert(std::is_void<Base>{} ||
+                  std::is_base_of<Base, decay_t<T>>{},
+                  "Value to store must be a derivative of the base.");
+    static_assert(S <= Size, "Value too large.");
+    static_assert(std::is_void<Base>{} || !std::is_const<Base>{} ||
+                  std::is_const<T>{},
+                  "Cannot assign const pointer to non-const pointer.");
+    return 0;
+  }
+  // Here we can store anything that has the same signature, which is
+  // relevant to the multiple-versions for move/copy support that I
+  // mentioned above.
+  //
+  size_t (*operate)(_mem::op, void*, void*);
+
+  // This is mutable so that get and the dereference operators can be
+  // const. Since we're modeling a pointer, we should preserve the
+  // difference in semantics between a pointer-to-const and a const
+  // pointer.
+  //
+  mutable typename std::aligned_storage<Size>::type buf;
+
+public:
+  using element_type = Base;
+  using pointer = Base*;
+
+  // Empty
+  static_ptr() noexcept : operate(nullptr) {}
+  static_ptr(std::nullptr_t) noexcept : operate(nullptr) {}
+  static_ptr& operator =(std::nullptr_t) noexcept {
+    reset();
+    return *this;
+  }
+  ~static_ptr() noexcept {
+    reset();
+  }
+
+  // Since other pointer-ish types have it
+  void reset() noexcept {
+    if (operate) {
+      operate(_mem::op::destroy, &buf, nullptr);
+      operate = nullptr;
+    }
+  }
+
+  // Set from another static pointer.
+  //
+  // Since the templated versions don't count for overriding the defaults
+  static_ptr(const static_ptr& rhs)
+    noexcept(std::is_nothrow_copy_constructible<Base>{}) : operate(rhs.operate) {
+    if (operate) {
+      operate(_mem::op::copy, &rhs.buf, &buf);
+    }
+  }
+  static_ptr(static_ptr&& rhs)
+    noexcept(std::is_nothrow_move_constructible<Base>{}) : operate(rhs.operate) {
+    if (operate) {
+      operate(_mem::op::move, &rhs.buf, &buf);
+    }
+  }
+
+  template<typename U, std::size_t S>
+  static_ptr(const static_ptr<U, S>& rhs)
+    noexcept(std::is_nothrow_copy_constructible<U>{}) : operate(rhs.operate) {
+    create_ward<U, S>();
+    if (operate) {
+      operate(_mem::op::copy, &rhs.buf, &buf);
+    }
+  }
+  template<typename U, std::size_t S>
+  static_ptr(static_ptr<U, S>&& rhs)
+    noexcept(std::is_nothrow_move_constructible<U>{}) : operate(rhs.operate) {
+    create_ward<U, S>();
+    if (operate) {
+      operate(_mem::op::move, &rhs.buf, &buf);
+    }
+  }
+
+  static_ptr& operator =(const static_ptr& rhs)
+    noexcept(std::is_nothrow_copy_constructible<Base>{}) {
+    reset();
+    if (rhs) {
+      operate = rhs.operate;
+      operate(_mem::op::copy,
+             const_cast<void*>(static_cast<const void*>(&rhs.buf)), &buf);
+    }
+    return *this;
+  }
+  static_ptr& operator =(static_ptr&& rhs)
+    noexcept(std::is_nothrow_move_constructible<Base>{}) {
+    reset();
+    if (rhs) {
+      operate = rhs.operate;
+      operate(_mem::op::move, &rhs.buf, &buf);
+    }
+    return *this;
+  }
+
+  template<typename U, std::size_t S>
+  static_ptr& operator =(const static_ptr<U, S>& rhs)
+    noexcept(std::is_nothrow_copy_constructible<U>{}) {
+    create_ward<U, S>();
+    reset();
+    if (rhs) {
+      operate = rhs.operate;
+      operate(_mem::op::copy,
+             const_cast<void*>(static_cast<const void*>(&rhs.buf)), &buf);
+    }
+    return *this;
+  }
+  template<typename U, std::size_t S>
+  static_ptr& operator =(static_ptr<U, S>&& rhs)
+    noexcept(std::is_nothrow_move_constructible<U>{}) {
+    create_ward<U, S>();
+    reset();
+    if (rhs) {
+      operate = rhs.operate;
+      operate(_mem::op::move, &rhs.buf, &buf);
+    }
+    return *this;
+  }
+
+  // In-place construction!
+  //
+  // This is basically what you want, and I didn't include value
+  // construction because in-place construction renders it
+  // unnecessary. Also it doesn't fit the pointer idiom as well.
+  //
+  template<typename T, typename... Args>
+  static_ptr(in_place_type_t<T>, Args&& ...args)
+    noexcept(std::is_nothrow_constructible<T, Args...>{})
+    : operate(&_mem::op_fun<T>){
+    static_assert((!std::is_nothrow_copy_constructible<Base>{} ||
+                  std::is_nothrow_copy_constructible<T>{}) &&
+                 (!std::is_nothrow_move_constructible<Base>{} ||
+                  std::is_nothrow_move_constructible<T>{}),
+                 "If declared type of static_ptr is nothrow "
+                 "move/copy constructible, then any "
+                 "type assigned to it must be as well. "
+                 "You can use reinterpret_pointer_cast "
+                 "to get around this limit, but don't "
+                 "come crying to me when the C++ "
+                 "runtime calls terminate().");
+    create_ward<T, sizeof(T)>();
+    new (&buf) T(std::forward<Args>(args)...);
+  }
+
+  // I occasionally get tempted to make an overload of the assignment
+  // operator that takes a tuple as its right-hand side to provide
+  // arguments.
+  //
+  template<typename T, typename... Args>
+  void emplace(Args&& ...args)
+    noexcept(std::is_nothrow_constructible<T, Args...>{}) {
+    create_ward<T, sizeof(T)>();
+    reset();
+    operate = &_mem::op_fun<T>;
+    new (&buf) T(std::forward<Args>(args)...);
+  }
+
+  // Access!
+  Base* get() const noexcept {
+    return operate ? reinterpret_cast<Base*>(&buf) : nullptr;
+  }
+  template<typename U = Base>
+  enable_if_t<!std::is_void<U>{}, Base*> operator->() const noexcept {
+    return get();
+  }
+  template<typename U = Base>
+  enable_if_t<!std::is_void<U>{}, Base&> operator *() const noexcept {
+    return *get();
+  }
+  operator bool() const noexcept {
+    return !!operate;
+  }
+
+  // Big wall of friendship
+  //
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> static_pointer_cast(const static_ptr<T, S>& p);
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> static_pointer_cast(static_ptr<T, S>&& p);
+
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> dynamic_pointer_cast(const static_ptr<T, S>& p);
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> dynamic_pointer_cast(static_ptr<T, S>&& p);
+
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> const_pointer_cast(const static_ptr<T, S>& p);
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> const_pointer_cast(static_ptr<T, S>&& p);
+
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> reinterpret_pointer_cast(const static_ptr<T, S>& p);
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> reinterpret_pointer_cast(static_ptr<T, S>&& p);
+
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> resize_pointer_cast(const static_ptr<T, S>& p);
+  template<typename U, std::size_t Z, typename T, std::size_t S>
+  friend static_ptr<U, Z> resize_pointer_cast(static_ptr<T, S>&& p);
+};
+
+// These are all modeled after the same ones for shared pointer.
+//
+// Also I'm annoyed that the standard library doesn't have
+// *_pointer_cast overloads for a move-only unique pointer. It's a
+// nice idiom. Having to release and reconstruct is obnoxious.
+//
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> static_pointer_cast(const static_ptr<T, S>& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  // Really, this is always true because static_cast either succeeds
+  // or fails to compile, but it prevents an unused variable warning
+  // and should be optimized out.
+  if (static_cast<U*>(p.get())) {
+    p.operate(_mem::op::copy, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> static_pointer_cast(static_ptr<T, S>&& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  if (static_cast<U*>(p.get())) {
+    p.operate(_mem::op::move, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+
+// Here the conditional is actually important and ensures we have the
+// same behavior as dynamic_cast.
+//
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> dynamic_pointer_cast(const static_ptr<T, S>& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  if (dynamic_cast<U*>(p.get())) {
+    p.operate(_mem::op::copy, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> dynamic_pointer_cast(static_ptr<T, S>&& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  if (dynamic_cast<U*>(p.get())) {
+    p.operate(_mem::op::move, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> const_pointer_cast(const static_ptr<T, S>& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  if (const_cast<U*>(p.get())) {
+    p.operate(_mem::op::copy, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> const_pointer_cast(static_ptr<T, S>&& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  if (const_cast<U*>(p.get())) {
+    p.operate(_mem::op::move, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+
+// I'm not sure if anyone will ever use this. I can imagine situations
+// where they might. It works, though!
+//
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> reinterpret_pointer_cast(const static_ptr<T, S>& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  p.operate(_mem::op::copy, &p.buf, &r.buf);
+  r.operate = p.operate;
+  return r;
+}
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> reinterpret_pointer_cast(static_ptr<T, S>&& p) {
+  static_assert(Z >= S,
+                "Value too large.");
+  static_ptr<U, Z> r;
+  p.operate(_mem::op::move, &p.buf, &r.buf);
+  r.operate = p.operate;
+  return r;
+}
+
+// This is the only way to move from a bigger static pointer into a
+// smaller static pointer. The size of the total data stored in the
+// pointer is checked at runtime and if the destination size is large
+// enough, we copy it over.
+//
+// I follow cast semantics. Since this is a pointer-like type, it
+// returns a null value rather than throwing.
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> resize_pointer_cast(const static_ptr<T, S>& p) {
+  static_assert(std::is_same<U, T>{},
+                "resize_pointer_cast only changes size, not type.");
+  static_ptr<U, Z> r;
+  if (Z >= p.operate(_mem::op::size, &p.buf, nullptr)) {
+    p.operate(_mem::op::copy, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+template<typename U, std::size_t Z, typename T, std::size_t S>
+static_ptr<U, Z> resize_pointer_cast(static_ptr<T, S>&& p) {
+  static_assert(std::is_same<U, T>{},
+                "resize_pointer_cast only changes size, not type.");
+  static_ptr<U, Z> r;
+  if (Z >= p.operate(_mem::op::size, &p.buf, nullptr)) {
+    p.operate(_mem::op::move, &p.buf, &r.buf);
+    r.operate = p.operate;
+  }
+  return r;
+}
+
+template<typename Base, std::size_t Size>
+bool operator ==(static_ptr<Base, Size> s, std::nullptr_t) {
+  return !s;
+}
+template<typename Base, std::size_t Size>
+bool operator ==(std::nullptr_t, static_ptr<Base, Size> s) {
+  return !s;
+}
+
+// Since `make_unique` and `make_shared` exist, we should follow their
+// lead.
+//
+template<typename Base, typename Derived = Base,
+         std::size_t Size = sizeof(Derived), typename... Args>
+static_ptr<Base, Size> make_static(Args&& ...args) {
+  return { in_place_type_t<Derived>{}, std::forward<Args>(args)... };
+}
+}
index 7be766079d16a1a08b8db06317d088e68bc38890..bb5d892da876ac9684388b9850d295ca966a86e5 100644 (file)
@@ -277,3 +277,6 @@ add_executable(unittest_bounded_key_counter
   $<TARGET_OBJECTS:unit-main>)
 target_link_libraries(unittest_bounded_key_counter global)
 add_ceph_unittest(unittest_bounded_key_counter)
+
+add_executable(unittest_static_ptr test_static_ptr.cc)
+add_ceph_unittest(unittest_static_ptr)
diff --git a/src/test/common/test_static_ptr.cc b/src/test/common/test_static_ptr.cc
new file mode 100644 (file)
index 0000000..4a464d7
--- /dev/null
@@ -0,0 +1,260 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+ */
+
+#include "common/static_ptr.h"
+#include <gtest/gtest.h>
+
+using ceph::static_ptr;
+using ceph::make_static;
+
+class base {
+public:
+  virtual int func() = 0;
+  virtual ~base() = default;
+};
+
+class sibling1 : public base {
+public:
+  int func() override { return 0; }
+};
+
+class sibling2 : public base {
+public:
+  int func() override { return 9; }
+  virtual int call(int) = 0;
+};
+
+class grandchild : public sibling2 {
+protected:
+  int val;
+public:
+  explicit grandchild(int val) : val(val) {}
+  virtual int call(int n) { return n * val; }
+};
+
+class great_grandchild : public grandchild {
+public:
+  great_grandchild(int val) : grandchild(val) {}
+  int call(int n) override { return n + val; }
+};
+
+TEST(StaticPtr, EmptyCreation) {
+  static_ptr<base, sizeof(grandchild)> p;
+  EXPECT_FALSE(p);
+  EXPECT_EQ(p, nullptr);
+  EXPECT_EQ(nullptr, p);
+  EXPECT_TRUE(p.get() == nullptr);
+}
+
+TEST(StaticPtr, CreationCall) {
+  {
+    static_ptr<base, sizeof(grandchild)> p(ceph::in_place_type_t<sibling1>{});
+    EXPECT_TRUE(p);
+    EXPECT_FALSE(p == nullptr);
+    EXPECT_FALSE(nullptr == p);
+    EXPECT_FALSE(p.get() == nullptr);
+    EXPECT_EQ(p->func(), 0);
+    EXPECT_EQ((*p).func(), 0);
+    EXPECT_EQ((p.get())->func(), 0);
+  }
+  {
+    auto p = make_static<base, sibling1>();
+    EXPECT_TRUE(p);
+    EXPECT_FALSE(p == nullptr);
+    EXPECT_FALSE(nullptr == p);
+    EXPECT_FALSE(p.get() == nullptr);
+    EXPECT_EQ(p->func(), 0);
+    EXPECT_EQ((*p).func(), 0);
+    EXPECT_EQ((p.get())->func(), 0);
+  }
+}
+
+TEST(StaticPtr, CreateReset) {
+  {
+    static_ptr<base, sizeof(grandchild)> p(ceph::in_place_type_t<sibling1>{});
+    EXPECT_EQ((p.get())->func(), 0);
+    p.reset();
+    EXPECT_FALSE(p);
+    EXPECT_EQ(p, nullptr);
+    EXPECT_EQ(nullptr, p);
+    EXPECT_TRUE(p.get() == nullptr);
+  }
+  {
+    static_ptr<base, sizeof(grandchild)> p(ceph::in_place_type_t<sibling1>{});
+    EXPECT_EQ((p.get())->func(), 0);
+    p = nullptr;
+    EXPECT_FALSE(p);
+    EXPECT_EQ(p, nullptr);
+    EXPECT_EQ(nullptr, p);
+    EXPECT_TRUE(p.get() == nullptr);
+  }
+}
+
+TEST(StaticPtr, CreateEmplace) {
+  static_ptr<base, sizeof(grandchild)> p(ceph::in_place_type_t<sibling1>{});
+  EXPECT_EQ((p.get())->func(), 0);
+  p.emplace<grandchild>(30);
+  EXPECT_EQ(p->func(), 9);
+}
+
+TEST(StaticPtr, CopyMove) {
+  // Won't compile. Good.
+  // static_ptr<base, sizeof(base)> p1(ceph::in_place_type_t<grandchild>{}, 3);
+
+  static_ptr<base, sizeof(base)> p1(ceph::in_place_type_t<sibling1>{});
+  static_ptr<base, sizeof(grandchild)> p2(ceph::in_place_type_t<grandchild>{},
+                                          3);
+
+  // This also does not compile. Good.
+  // p1 = p2;
+  p2 = p1;
+  EXPECT_EQ(p1->func(), 0);
+
+  p2 = std::move(p1);
+  EXPECT_EQ(p1->func(), 0);
+}
+
+TEST(StaticPtr, ImplicitUpcast) {
+  static_ptr<base, sizeof(grandchild)> p1;
+  static_ptr<sibling2, sizeof(grandchild)> p2(ceph::in_place_type_t<grandchild>{}, 3);
+
+  p1 = p2;
+  EXPECT_EQ(p1->func(), 9);
+
+  p1 = std::move(p2);
+  EXPECT_EQ(p1->func(), 9);
+
+  p2.reset();
+
+  // Doesn't compile. Good.
+  // p2 = p1;
+}
+
+TEST(StaticPtr, StaticCast) {
+  static_ptr<base, sizeof(grandchild)> p1(ceph::in_place_type_t<grandchild>{}, 3);
+  static_ptr<sibling2, sizeof(grandchild)> p2;
+
+  p2 = ceph::static_pointer_cast<sibling2, sizeof(grandchild)>(p1);
+  EXPECT_EQ(p2->func(), 9);
+  EXPECT_EQ(p2->call(10), 30);
+
+  p2 = ceph::static_pointer_cast<sibling2, sizeof(grandchild)>(std::move(p1));
+  EXPECT_EQ(p2->func(), 9);
+  EXPECT_EQ(p2->call(10), 30);
+}
+
+TEST(StaticPtr, DynamicCast) {
+  static constexpr auto sz = sizeof(great_grandchild);
+  {
+    static_ptr<base, sz> p1(ceph::in_place_type_t<grandchild>{}, 3);
+    auto p2 = ceph::dynamic_pointer_cast<great_grandchild, sz>(p1);
+    EXPECT_FALSE(p2);
+  }
+  {
+    static_ptr<base, sz> p1(ceph::in_place_type_t<grandchild>{}, 3);
+    auto p2 = ceph::dynamic_pointer_cast<great_grandchild, sz>(std::move(p1));
+    EXPECT_FALSE(p2);
+  }
+
+  {
+    static_ptr<base, sz> p1(ceph::in_place_type_t<grandchild>{}, 3);
+    auto p2 = ceph::dynamic_pointer_cast<grandchild, sz>(p1);
+    EXPECT_TRUE(p2);
+    EXPECT_EQ(p2->func(), 9);
+    EXPECT_EQ(p2->call(10), 30);
+  }
+  {
+    static_ptr<base, sz> p1(ceph::in_place_type_t<grandchild>{}, 3);
+    auto p2 = ceph::dynamic_pointer_cast<grandchild, sz>(std::move(p1));
+    EXPECT_TRUE(p2);
+    EXPECT_EQ(p2->func(), 9);
+    EXPECT_EQ(p2->call(10), 30);
+  }
+}
+
+class constable {
+public:
+  int foo() {
+    return 2;
+  }
+  int foo() const {
+    return 5;
+  }
+};
+
+TEST(StaticPtr, ConstCast) {
+  static constexpr auto sz = sizeof(constable);
+  {
+    auto p1 = make_static<const constable>();
+    static_assert(std::is_const<decltype(p1)::element_type>{},
+                  "Things are not as const as they ought to be.");
+    EXPECT_EQ(p1->foo(), 5);
+    auto p2 = ceph::const_pointer_cast<constable, sz>(p1);
+    static_assert(!std::is_const<decltype(p2)::element_type>{},
+                  "Things are more const than they ought to be.");
+    EXPECT_TRUE(p2);
+    EXPECT_EQ(p2->foo(), 2);
+  }
+  {
+    auto p1 = make_static<const constable>();
+    EXPECT_EQ(p1->foo(), 5);
+    auto p2 = ceph::const_pointer_cast<constable, sz>(std::move(p1));
+    static_assert(!std::is_const<decltype(p2)::element_type>{},
+                  "Things are more const than they ought to be.");
+    EXPECT_TRUE(p2);
+    EXPECT_EQ(p2->foo(), 2);
+  }
+}
+
+TEST(StaticPtr, ReinterpretCast) {
+  static constexpr auto sz = sizeof(grandchild);
+  {
+    auto p1 = make_static<grandchild>(3);
+    auto p2 = ceph::reinterpret_pointer_cast<constable, sz>(p1);
+    static_assert(std::is_same<decltype(p2)::element_type, constable>{},
+                  "Reinterpret is screwy.");
+    auto p3 = ceph::reinterpret_pointer_cast<grandchild, sz>(p2);
+    static_assert(std::is_same<decltype(p3)::element_type, grandchild>{},
+                  "Reinterpret is screwy.");
+    EXPECT_EQ(p3->func(), 9);
+    EXPECT_EQ(p3->call(10), 30);
+  }
+  {
+    auto p1 = make_static<grandchild>(3);
+    auto p2 = ceph::reinterpret_pointer_cast<constable, sz>(std::move(p1));
+    static_assert(std::is_same<decltype(p2)::element_type, constable>{},
+                  "Reinterpret is screwy.");
+    auto p3 = ceph::reinterpret_pointer_cast<grandchild, sz>(std::move(p2));
+    static_assert(std::is_same<decltype(p3)::element_type, grandchild>{},
+                  "Reinterpret is screwy.");
+    EXPECT_EQ(p3->func(), 9);
+    EXPECT_EQ(p3->call(10), 30);
+  }
+}
+
+struct exceptional {
+  exceptional() = default;
+  exceptional(const exceptional& e) {
+    throw std::exception();
+  }
+  exceptional(exceptional&& e) {
+    throw std::exception();
+  }
+};
+
+TEST(StaticPtr, Exceptional) {
+  static_ptr<exceptional> p1(ceph::in_place_type_t<exceptional>{});
+  EXPECT_ANY_THROW(static_ptr<exceptional> p2(p1));
+  EXPECT_ANY_THROW(static_ptr<exceptional> p2(std::move(p1)));
+}