Vastly simplified llarp::util::memFn

There is a huge pile of unnecessary machinery here that can be solved
with a few lambdas and some member function pointer type deduction.
This commit is contained in:
Jason Rhinelander 2020-02-21 23:11:20 -04:00
parent b4440094b0
commit fe61367a87
14 changed files with 65 additions and 871 deletions

View File

@ -31,11 +31,7 @@ set(LIB_UTIL_SRC
util/logging/win32_logger.cpp
util/lokinet_init.c
util/mem.cpp
util/meta/memfn_traits.cpp
util/meta/memfn.cpp
util/meta/object.cpp
util/meta/traits.cpp
util/meta/variant.cpp
util/printer.cpp
util/status.cpp
util/str.cpp

View File

@ -1,88 +1,68 @@
#ifndef LLARP_UTIL_MEMFN
#define LLARP_UTIL_MEMFN
#include <util/meta/memfn_traits.hpp>
#include <util/meta/object.hpp>
#include <util/meta/traits.hpp>
#include <functional>
#include <type_traits>
#include <utility>
#include <memory>
namespace llarp
{
namespace util
{
template < typename Obj >
struct MemFnDereference
// Wraps a member function and instance into a callable object that invokes
// the method (non-const overload).
template <
typename Return, typename Class, typename Derived, typename... Arg,
typename =
std::enable_if_t< std::is_base_of< Class, Derived >::value > >
auto
memFn(Return (Class::*f)(Arg...), Derived* self)
{
// clang-format off
static inline Obj& derefImp(Obj& obj, std::false_type)
{
return obj;
}
return [f, self](Arg... args) {
return (self->*f)(std::forward< Arg >(args)...);
};
}
template < typename Type >
static inline Obj& derefImp(Type& obj, std::true_type)
{
return *obj;
}
template < typename Type >
static inline Obj& derefImp(const Type& obj, std::true_type)
{
return *obj;
}
template < typename Type >
static inline Obj& deref(Type& obj)
{
return derefImp(obj, traits::is_pointy< Type >());
}
template < typename Type >
static inline Obj& deref(const Type& obj)
{
return derefImp(obj, traits::is_pointy< Type >());
}
// clang-format on
};
template < typename Prototype, typename Instance >
class MemFn
// Wraps a member function and instance into a lambda that invokes the
// method (const overload).
template <
typename Return, typename Class, typename Derived, typename... Arg,
typename =
std::enable_if_t< std::is_base_of< Class, Derived >::value > >
auto
memFn(Return (Class::*f)(Arg...) const, const Derived* self)
{
using traits = MemFnTraits< Prototype >;
return [f, self](Arg... args) {
return (self->*f)(std::forward< Arg >(args)...);
};
}
static_assert(traits::IsMemFn, "");
public:
using result_type = typename traits::result_type;
private:
Prototype m_func;
object::Proxy< Instance > m_instance;
using Deref = MemFnDereference< typename traits::class_type >;
public:
MemFn(Prototype prototype, const Instance& instance)
: m_func(prototype), m_instance(instance)
{
}
template < typename... Args >
result_type
operator()(Args&&... args) const
{
return (Deref::deref(m_instance.value())
.*m_func)(std::forward< Args >(args)...);
}
};
template < typename Prototype, typename Instance >
MemFn< Prototype, Instance >
memFn(Prototype prototype, const Instance& instance)
// Wraps a member function and shared pointer to an instance into a lambda
// that invokes the method.
template <
typename Return, typename Class, typename Derived, typename... Arg,
typename =
std::enable_if_t< std::is_base_of< Class, Derived >::value > >
auto
memFn(Return (Class::*f)(Arg...), std::shared_ptr< Derived > self)
{
return MemFn< Prototype, Instance >(prototype, instance);
return [f, self = std::move(self)](Arg... args) {
return (self.get()->*f)(std::forward< Arg >(args)...);
};
}
// Wraps a member function and shared pointer to an instance into a lambda
// that invokes the method (const method overload).
template <
typename Return, typename Class, typename Derived, typename... Arg,
typename =
std::enable_if_t< std::is_base_of< Class, Derived >::value > >
auto
memFn(Return (Class::*f)(Arg...) const, std::shared_ptr< Derived > self)
{
return [f, self = std::move(self)](Arg... args) {
return (self.get()->*f)(std::forward< Arg >(args)...);
};
}
} // namespace util

View File

@ -1 +0,0 @@
#include <util/meta/memfn_traits.hpp>

View File

@ -1,107 +0,0 @@
#ifndef LLARP_UTIL_MEMFN_TRAITS
#define LLARP_UTIL_MEMFN_TRAITS
#include <util/meta/object.hpp>
#include <util/meta/traits.hpp>
#include <functional>
#include <utility>
namespace llarp
{
namespace util
{
template < typename Prototype, typename TestPrototype >
struct MemFnTraitsImpl;
template < typename Prototype >
struct MemFnTraits : public MemFnTraitsImpl< Prototype, Prototype >
{
};
template < typename Prototype, typename Return, typename Type,
typename... Args >
class MemFnTraitsClass
{
using NonCVTag = traits::Tag< 0 >;
using ConstTag = traits::Tag< 1 >;
using VolTag = traits::Tag< 2 >;
using ConstVolTag = traits::Tag< 3 >;
// clang-format off
static constexpr NonCVTag test(Return (Type::*)(Args...));
static constexpr ConstTag test(Return (Type::*)(Args...) const);
static constexpr VolTag test(Return (Type::*)(Args...) volatile);
static constexpr ConstVolTag test(Return (Type::*)(Args...) const volatile);
// clang-format on
public:
static constexpr bool IsConst =
((sizeof((test)((Prototype)0)) - 1) & 1) != 0;
static constexpr bool IsVolatile =
((sizeof((test)((Prototype)0)) - 1) & 2) != 0;
using ctype = std::conditional_t< IsConst, const Type, Type >;
using type = std::conditional_t< IsVolatile, volatile ctype, ctype >;
};
template < typename Prototype, typename Return, typename Type,
typename... Args >
struct MemFnTraitsImpl< Prototype, Return (Type::*)(Args...) >
{
static constexpr bool IsMemFn = true;
using class_type =
typename MemFnTraitsClass< Prototype, Return, Type, Args... >::type;
using result_type = Return;
};
template < typename Prototype, typename Return, typename Type,
typename... Args >
struct MemFnTraitsImpl< Prototype, Return (Type::*)(Args...) const >
{
static constexpr bool IsMemFn = true;
using class_type =
typename MemFnTraitsClass< Prototype, Return, Type, Args... >::type;
using result_type = Return;
};
template < typename Prototype, typename Return, typename Type,
typename... Args >
struct MemFnTraitsImpl< Prototype, Return (Type::*)(Args...) volatile >
{
static constexpr bool IsMemFn = true;
using class_type =
typename MemFnTraitsClass< Prototype, Return, Type, Args... >::type;
using result_type = Return;
};
template < typename Prototype, typename Return, typename Type,
typename... Args >
struct MemFnTraitsImpl< Prototype,
Return (Type::*)(Args...) const volatile >
{
static constexpr bool IsMemFn = true;
using class_type =
typename MemFnTraitsClass< Prototype, Return, Type, Args... >::type;
using result_type = Return;
};
template < typename Prototype, typename TestPrototype >
struct MemFnTraitsImpl
{
static constexpr bool IsMemFn = false;
using result_type = void;
using class_type = void;
};
} // namespace util
} // namespace llarp
#endif

View File

@ -1 +0,0 @@
#include <util/meta/object.hpp>

View File

@ -1,466 +0,0 @@
#ifndef LLARP_OBJECT_HPP
#define LLARP_OBJECT_HPP
#include <util/thread/threading.hpp>
#include <atomic>
#include <nonstd/optional.hpp>
#include <vector>
namespace llarp
{
namespace object
{
/// Provide a buffer, capable of holding a single `Value` object.
/// This is useful for node-based data structures.
/// Note:
/// - This union explicitly does *not* manage the lifetime of the object,
/// explicit calls to the constructor/destructor must be made.
template < typename Value >
union Buffer {
private:
char m_buffer[sizeof(Value)];
char m_align[alignof(Value)];
public:
Value*
address()
{
return reinterpret_cast< Value* >(static_cast< void* >(m_buffer));
}
const Value*
address() const
{
return reinterpret_cast< Value* >(static_cast< void* >(m_buffer));
}
char*
buffer()
{
return m_buffer;
}
const char*
buffer() const
{
return m_buffer;
}
Value&
value()
{
return *reinterpret_cast< Value* >(this);
}
const Value&
value() const
{
return *reinterpret_cast< const Value* >(this);
}
};
template < typename Value >
class Proxy
{
Buffer< Value > m_value;
Proxy&
operator=(const Proxy&) = delete;
public:
Proxy()
{
::new(m_value.buffer()) Value();
}
Proxy(const Proxy& other)
{
::new(m_value.buffer()) Value(other.value());
}
Proxy(const Value& value)
{
::new(m_value.buffer()) Value(value);
}
// template < typename... Args >
// Proxy(Args&&... args)
// {
// ::new(m_value.buffer()) Value(std::forward< Args >(args)...);
// }
~Proxy()
{
m_value.address()->~Value();
}
Value&
value()
{
return m_value.value();
}
const Value&
value() const
{
return m_value.value();
}
};
template < typename Value >
class Catalog;
template < typename Value >
class CatalogIterator;
template < typename Value >
class CatalogCleaner
{
Catalog< Value >* m_catalog;
typename Catalog< Value >::Node* m_node;
bool m_shouldDelete;
CatalogCleaner(const CatalogCleaner&) = delete;
CatalogCleaner&
operator=(const CatalogCleaner&) = delete;
public:
explicit CatalogCleaner(Catalog< Value >* catalog)
: m_catalog(catalog), m_node(nullptr), m_shouldDelete(false)
{
}
~CatalogCleaner();
void
manageNode(typename Catalog< Value >::Node* node, bool shouldDelete)
{
m_node = node;
m_shouldDelete = shouldDelete;
}
void
releaseNode()
{
m_node = nullptr;
}
void
release()
{
releaseNode();
m_catalog = nullptr;
}
};
/// A pooling catalog of objects, referred to by a 32-bit handle
template < typename Value >
class Catalog
{
enum
{
INDEX_MASK = 0X007FFFFF,
BUSY_INDICATOR = 0x00800000,
GENERATION_INC = 0x01000000,
GENERATION_MASK = 0XFF000000
};
struct Node
{
union Payload {
Buffer< Value > m_buffer;
Node* m_next;
};
Payload m_payload;
int32_t m_handle;
};
std::vector< Node* > m_nodes GUARDED_BY(m_mutex);
Node* m_next;
std::atomic_size_t m_size;
mutable util::Mutex m_mutex;
friend class CatalogCleaner< Value >;
friend class CatalogIterator< Value >;
static Value*
getValue(Node* node)
{
return node->m_payload.m_buffer.address();
}
void
freeNode(Node* node)
{
node->m_handle += GENERATION_INC;
node->m_handle &= ~BUSY_INDICATOR;
node->m_payload.m_next = m_next;
m_next = node;
}
Node*
findNode(int32_t handle) const REQUIRES_SHARED(m_mutex)
{
int32_t index = handle & INDEX_MASK;
if(0 > index || index >= static_cast< int32_t >(m_nodes.size())
|| !(handle & BUSY_INDICATOR))
{
return nullptr;
}
Node* node = m_nodes[index];
return (node->m_handle == handle) ? node : nullptr;
}
public:
Catalog() : m_next(nullptr), m_size(0)
{
}
~Catalog()
{
removeAll();
}
int32_t
add(const Value& value)
{
int32_t handle;
util::Lock l(m_mutex);
CatalogCleaner< Value > guard(this);
Node* node;
if(m_next)
{
node = m_next;
m_next = node->m_payload.m_next;
guard.manageNode(node, false);
}
else
{
assert(m_nodes.size() < BUSY_INDICATOR);
node = new Node;
guard.manageNode(node, true);
m_nodes.push_back(node);
node->m_handle = static_cast< int32_t >(m_nodes.size() - 1);
guard.manageNode(node, false);
}
node->m_handle |= BUSY_INDICATOR;
handle = node->m_handle;
// construct into the node.
::new(getValue(node)) Value(value);
guard.release();
++m_size;
return handle;
}
bool
remove(int32_t handle, Value* value = nullptr)
{
util::Lock l(m_mutex);
Node* node = findNode(handle);
if(!node)
{
return false;
}
Value* val = getValue(node);
if(value)
{
*value = *val;
}
val->~Value();
freeNode(node);
--m_size;
return true;
}
void
removeAll(std::vector< Value >* output = nullptr)
{
util::Lock l(m_mutex);
for(Node* node : m_nodes)
{
if(node->m_handle & BUSY_INDICATOR)
{
Value* value = getValue(node);
if(output)
{
output->push_back(*value);
}
value->~Value();
}
delete node;
}
m_nodes.clear();
m_next = nullptr;
m_size = 0;
}
bool
replace(const Value& newValue, int32_t handle)
{
util::Lock l(m_mutex);
Node* node = findNode(handle);
if(!node)
{
return false;
}
Value* value = getValue(node);
value->~Value();
// construct into the node.
::new(value) Value(newValue);
return true;
}
nonstd::optional< Value >
find(int32_t handle)
{
auto l = util::shared_lock(m_mutex);
Node* node = findNode(handle);
if(!node)
{
return {};
}
return *getValue(node);
}
size_t
size() const
{
return m_size;
}
/// introduced for testing only. verify the current state of the catalog.
bool
verify() const;
};
template < typename Value >
class SCOPED_CAPABILITY CatalogIterator
{
const Catalog< Value >* m_catalog;
size_t m_index;
std::shared_lock<util::Mutex> lock;
CatalogIterator(const CatalogIterator&) = delete;
CatalogIterator&
operator=(const CatalogIterator&) = delete;
public:
explicit CatalogIterator(const Catalog< Value >* catalog)
ACQUIRE_SHARED(catalog->m_mutex)
: m_catalog(catalog), m_index(-1), lock(m_catalog->m_mutex)
{
operator++();
}
void
operator++() NO_THREAD_SAFETY_ANALYSIS
{
m_index++;
while(m_index < m_catalog->m_nodes.size()
&& !(m_catalog->m_nodes[m_index]->m_handle
& Catalog< Value >::BUSY_INDICATOR))
{
m_index++;
}
}
explicit operator bool() const NO_THREAD_SAFETY_ANALYSIS
{
return m_index < m_catalog->m_nodes.size();
}
std::pair< int32_t, Value >
operator()() const NO_THREAD_SAFETY_ANALYSIS
{
auto* node = m_catalog->m_nodes[m_index];
return {node->m_handle, *Catalog< Value >::getValue(node)};
}
};
template < typename Value >
CatalogCleaner< Value >::~CatalogCleaner()
{
if(m_catalog && m_node)
{
if(m_shouldDelete)
{
// We call the destructor elsewhere.
operator delete(m_node);
}
else
{
m_catalog->freeNode(m_node);
}
}
}
template < typename Value >
bool
Catalog< Value >::verify() const
{
util::Lock l(m_mutex);
if(m_nodes.size() < m_size)
{
return false;
}
size_t busyCount = 0;
for(size_t i = 0; i < m_nodes.size(); i++)
{
if((m_nodes[i]->m_handle & INDEX_MASK) != i)
{
return false;
}
if(m_nodes[i]->m_handle & BUSY_INDICATOR)
{
busyCount++;
}
}
if(m_size != busyCount)
{
return false;
}
size_t freeCount = 0;
for(Node* p = m_next; p != nullptr; p = p->m_payload.m_next)
{
freeCount++;
}
if(freeCount + busyCount != m_nodes.size())
{
return false;
}
return true;
}
} // namespace object
} // namespace llarp
#endif

View File

@ -1,8 +1,6 @@
#ifndef LLARP_TRAITS_HPP
#define LLARP_TRAITS_HPP
#include <absl/meta/type_traits.h>
#include <cstddef>
#include <type_traits>
#include <utility>
@ -12,9 +10,18 @@ namespace llarp
{
namespace traits
{
using absl::conjunction;
using absl::disjunction;
using absl::void_t;
#ifdef __cpp_lib_void_t
using std::void_t;
#else
/// C++17 void_t backport
template < typename... Ts >
struct void_t_impl
{
using type = void;
};
template < typename... Ts >
using void_t = typename void_t_impl< Ts... >::type;
#endif
/// Represents the empty type
struct Bottom

View File

@ -1 +0,0 @@
#include <util/meta/variant.hpp>

View File

@ -1,45 +0,0 @@
#ifndef LLARP_VARIANT_HPP
#define LLARP_VARIANT_HPP
#include <absl/types/variant.h>
namespace llarp
{
namespace util
{
template < typename... Ts >
struct _overloaded;
template < typename T, typename... Ts >
struct _overloaded< T, Ts... > : T, _overloaded< Ts... >
{
_overloaded(T&& t, Ts&&... ts)
: T(t), _overloaded< Ts... >(std::forward< Ts >(ts)...)
{
}
using T::operator();
using _overloaded< Ts... >::operator();
};
template < typename T >
struct _overloaded< T > : T
{
_overloaded(T&& t) : T(t)
{
}
using T::operator();
};
template < typename... Ts >
constexpr auto
overloaded(Ts&&... ts) -> _overloaded< Ts... >
{
return _overloaded< Ts... >(std::forward< Ts >(ts)...);
}
} // namespace util
} // namespace llarp
#endif

View File

@ -3,7 +3,6 @@
#include <util/string_view.hpp>
#include <util/meta/traits.hpp>
#include <util/meta/variant.hpp>
#include <functional>
#include <iostream>

View File

@ -8,7 +8,6 @@
#include <vector>
#include <string>
#include <algorithm>
#include <absl/types/variant.h>
namespace llarp
{

View File

@ -32,7 +32,6 @@ list(APPEND TEST_SRC
test_llarp_router_contact.cpp
test_md5.cpp
util/meta/test_llarp_util_memfn.cpp
util/meta/test_llarp_util_object.cpp
util/meta/test_llarp_util_traits.cpp
util/test_llarp_util_aligned.cpp
util/test_llarp_util_bencode.cpp

View File

@ -40,8 +40,8 @@ TEST(MemFn, call)
ASSERT_EQ(11, util::memFn(&Foo::arg, &foo)(10));
ASSERT_EQ(9, util::memFn(&Foo::constArg, &foo)(10));
ASSERT_TRUE(util::memFn(&Foo::constEmpty, foo)());
ASSERT_EQ(9, util::memFn(&Foo::constArg, foo)(10));
ASSERT_TRUE(util::memFn(&Foo::constEmpty, &foo)());
ASSERT_EQ(9, util::memFn(&Foo::constArg, &foo)(10));
}
template < typename T >
@ -54,7 +54,6 @@ TYPED_TEST_SUITE_P(MemFnType);
TYPED_TEST_P(MemFnType, Smoke)
{
TypeParam foo{};
ASSERT_TRUE(util::memFn(&Foo::constEmpty, foo)());
ASSERT_TRUE(util::memFn(&Foo::constEmpty, &foo)());
}

View File

@ -1,164 +0,0 @@
#include <util/meta/object.hpp>
#include <util/thread/barrier.hpp>
#include <array>
#include <thread>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
using namespace llarp::object;
TEST(Object, VerifySize)
{
static_assert(sizeof(Buffer< char >) == sizeof(char), "");
static_assert(sizeof(Buffer< int >) == sizeof(int), "");
static_assert(sizeof(Buffer< double >) == sizeof(double), "");
static_assert(sizeof(Buffer< std::string >) == sizeof(std::string), "");
}
TEST(Object, Inplace)
{
// Verify we can create and destroy a type with a non-trivial destructor
Buffer< std::vector< std::string > > strBuf;
new(strBuf.buffer()) std::vector< std::string >(100, "abc");
strBuf.value().~vector();
}
TEST(Catalog, smoke)
{
const double value1 = 1.0;
const double value2 = 2.0;
int32_t handle1 = -1;
int32_t handle2 = -1;
Catalog< double > catalog;
handle1 = catalog.add(value1);
catalog.remove(handle1);
for(size_t j = 0; j < 5; ++j)
{
for(size_t i = 1; i < 256; ++i)
{
ASSERT_FALSE(catalog.find(handle1));
handle2 = catalog.add(value2);
catalog.remove(handle2);
}
handle2 = catalog.add(value2);
ASSERT_EQ(handle1, handle2);
ASSERT_TRUE(catalog.find(handle1));
nonstd::optional< double > result = catalog.find(handle1);
ASSERT_TRUE(result);
ASSERT_EQ(value2, result);
catalog.remove(handle2);
}
}
TEST(Catalog, Iterator)
{
static constexpr size_t THREAD_COUNT = 10;
static constexpr size_t ITERATION_COUNT = 1000;
static constexpr int32_t MAX = std::numeric_limits< int32_t >::max();
std::array< std::thread, THREAD_COUNT + 3 > threads;
using llarp::util::Barrier;
using Iterator = CatalogIterator< int32_t >;
using Cat = Catalog< int32_t >;
Barrier barrier(THREAD_COUNT + 3);
Cat catalog;
// Repeatedly remove + add values from the catalog
for(size_t i = 0; i < THREAD_COUNT; ++i)
{
threads[i] = std::thread(
[](Barrier *b, Cat *cat, int32_t id) {
b->Block();
for(size_t j = 0; j < ITERATION_COUNT; ++j)
{
int32_t handle = cat->add(id);
nonstd::optional< int32_t > res = cat->find(handle);
ASSERT_TRUE(res);
ASSERT_EQ(res.value(), id);
ASSERT_TRUE(cat->replace(MAX - id, handle));
res = cat->find(handle);
ASSERT_TRUE(res);
ASSERT_EQ(MAX - id, res.value());
int32_t removed = 0;
ASSERT_TRUE(cat->remove(handle, &removed));
ASSERT_EQ(removed, MAX - id);
ASSERT_FALSE(cat->find(handle));
}
},
&barrier, &catalog, i);
}
// Verify the length constraint is never violated
threads[THREAD_COUNT] = std::thread(
[](Barrier *b, Cat *cat) {
b->Block();
for(size_t i = 0; i < ITERATION_COUNT; ++i)
{
size_t size = cat->size();
ASSERT_LE(size, THREAD_COUNT);
}
},
&barrier, &catalog);
// Verify that iteration always produces a valid state
threads[THREAD_COUNT + 1] = std::thread(
[](Barrier *b, Cat *cat) {
b->Block();
for(size_t i = 0; i < ITERATION_COUNT; ++i)
{
int32_t arr[100];
size_t size = 0;
for(Iterator it(cat); it; ++it)
{
arr[size++] = it().second;
}
for(int32_t j = 0; j < 100; j++)
{
// value must be valid
bool present = false;
for(int32_t id = 0; id < static_cast< int32_t >(THREAD_COUNT); id++)
{
if(id == arr[j] || MAX - id == arr[j])
{
present = true;
break;
}
}
(void)present;
// no duplicate should be there
for(size_t k = j + 1; k < size; k++)
{
ASSERT_NE(arr[j], arr[k]);
}
}
}
},
&barrier, &catalog);
// And that we don't have an invalid catalog
threads[THREAD_COUNT + 2] = std::thread(
[](Barrier *b, Cat *cat) {
b->Block();
for(size_t i = 0; i < ITERATION_COUNT; ++i)
{
cat->verify();
}
},
&barrier, &catalog);
for(std::thread &t : threads)
{
t.join();
}
}