mirror of
https://github.com/ceph/ceph
synced 2025-02-23 02:57:21 +00:00
Merge pull request #19079 from adamemerson/wip-static-ptr
Static Pointer
This commit is contained in:
commit
134734ab74
442
src/common/static_ptr.h
Normal file
442
src/common/static_ptr.h
Normal file
@ -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)... };
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
260
src/test/common/test_static_ptr.cc
Normal file
260
src/test/common/test_static_ptr.cc
Normal file
@ -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)));
|
||||
}
|
Loading…
Reference in New Issue
Block a user