Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions src/aether-miscpp/types/method_ptr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2026 Aethernet 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.
*/

#ifndef AETHER_MISCPP_TYPES_METHOD_PTR_H_
#define AETHER_MISCPP_TYPES_METHOD_PTR_H_

#include <type_traits>
#include <utility>

namespace ae {
/**
* \brief Non-owning wrapper for supported function and member-function
* pointers.
*
* Member-function wrappers retain a raw instance pointer and do not manage its
* lifetime. Only unqualified and const member functions are supported;
* volatile-qualified and ref-qualified member functions are unsupported.
*/

template <auto M>
struct MethodPtr;

namespace method_ptr_internal {
template <typename T>
struct IsMethodPtr : std::false_type {};

template <auto Method>
struct IsMethodPtr<MethodPtr<Method>> : std::true_type {};
} // namespace method_ptr_internal

template <typename T>
concept MethodPtrType = method_ptr_internal::IsMethodPtr<T>::value;

// Free-function pointer.
template <typename TRet, typename... TArgs, TRet (*Method)(TArgs...)>
struct MethodPtr<Method> {
static constexpr auto kMethod = Method;
static TRet Invoke(TArgs... args) {
return Method(std::forward<TArgs>(args)...);
}

TRet operator()(TArgs... args) const {
return Invoke(std::forward<TArgs>(args)...);
}
};

// Unqualified class-member function pointer.
template <typename T, typename TRet, typename... TArgs,
TRet (T::*Method)(TArgs...)>
struct MethodPtr<Method> {
static constexpr auto kMethod = Method;
static TRet Invoke(T* instance, TArgs... args) {
return (instance->*Method)(std::forward<TArgs>(args)...);
}

TRet operator()(TArgs... args) const {
return Invoke(instance, std::forward<TArgs>(args)...);
}

T* instance;
};

// Const class-member function pointer.
template <typename T, typename TRet, typename... TArgs,
TRet (T::*Method)(TArgs...) const>
struct MethodPtr<Method> {
static constexpr auto kMethod = Method;
static TRet Invoke(T const* instance, TArgs... args) {
return (instance->*Method)(std::forward<TArgs>(args)...);
}

TRet operator()(TArgs... args) const {
return Invoke(instance, std::forward<TArgs>(args)...);
}

T const* instance;
};

} // namespace ae

#endif // AETHER_MISCPP_TYPES_METHOD_PTR_H_
180 changes: 92 additions & 88 deletions src/aether-miscpp/types/small_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@
#ifndef AETHER_MISCPP_TYPES_SMALL_FUNCTION_H_
#define AETHER_MISCPP_TYPES_SMALL_FUNCTION_H_

#include <memory>
#include <cstddef>
#include <cassert>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>

#include "aether-miscpp/types/aligned_storage.h"
#include "aether-miscpp/meta/function_signature.h"
#include "aether-miscpp/types/aligned_storage.h"
#include "aether-miscpp/types/method_ptr.h" // IWYU pragma: exports

namespace ae {
namespace small_function_internal {
Expand All @@ -44,25 +50,18 @@ struct Invoker {
template <typename TCallable>
static TRet InvokeCallable(void* self, TArgs&&... args) {
if constexpr (std::is_void_v<TRet>) {
std::launder(static_cast<TCallable*>(self))
->operator()(std::forward<TArgs>(args)...);
std::invoke(*std::launder(static_cast<TCallable*>(self)),
std::forward<TArgs>(args)...);
} else {
return std::launder(static_cast<TCallable*>(self))
->operator()(std::forward<TArgs>(args)...);
return std::invoke(*std::launder(static_cast<TCallable*>(self)),
std::forward<TArgs>(args)...);
}
}

// Invoke for member functions
template <typename T, auto Method>
static TRet InvokeMemberFunction(void* self, TArgs&&... args) {
auto* instance = reinterpret_cast<T*>(*static_cast<std::uintptr_t*>(self));
return (instance->*Method)(std::forward<TArgs>(args)...);
}

// Invoke for free functions
template <typename TFunction>
static TRet InvokeFreeFunction(void* self, TArgs&&... args) {
auto method = reinterpret_cast<TRet (*)(TArgs...)>(
*static_cast<std::uintptr_t*>(self));
auto method = *std::launder(static_cast<TFunction*>(self));
return method(std::forward<TArgs>(args)...);
}
};
Expand All @@ -71,21 +70,14 @@ template <typename TCallable>
static void Manage(void* a, void* b, Operation op) noexcept {
switch (op) {
case Operation::kMove: {
{
if constexpr (std::is_trivially_move_constructible_v<TCallable>) {
std::memcpy(b, a, sizeof(TCallable));
} else {
new (b)
TCallable{std::move(*std::launder(static_cast<TCallable*>(a)))};
}
break;
}
case Operation::kDestroy: {
if constexpr (!std::is_trivially_destructible_v<TCallable>) {
std::destroy_at(std::launder(static_cast<TCallable*>(a)));
}
break;
new (b) TCallable{std::move(*std::launder(static_cast<TCallable*>(a)))};
break;
}
case Operation::kDestroy: {
if constexpr (!std::is_trivially_destructible_v<TCallable>) {
std::destroy_at(std::launder(static_cast<TCallable*>(a)));
}
break;
}
}
}
Expand All @@ -96,37 +88,30 @@ static constexpr auto VTableForT = VTable<TRet, TArgs...>{
Manage<TCallable>,
};

template <typename T, auto Method, typename TRet, typename... TArgs>
static constexpr auto VTableForClassMember = VTable<TRet, TArgs...>{
Invoker<TRet, TArgs...>::template InvokeMemberFunction<T, Method>,
Manage<T*>,
};

template <typename TRet, typename... TArgs>
template <typename TFunction, typename TRet, typename... TArgs>
static constexpr auto VTableForFreeFunction = VTable<TRet, TArgs...>{
Invoker<TRet, TArgs...>::InvokeFreeFunction,
Manage<TRet (*)(TArgs...)>,
};
} // namespace small_function_internal

template <auto Method>
struct MethodPtr;

// class member function pointer
template <typename T, typename TRet, typename... TArgs,
TRet (T::*Method)(TArgs...)>
struct MethodPtr<Method> {
static constexpr auto kMethod = Method;
T* instance;
Invoker<TRet, TArgs...>::template InvokeFreeFunction<TFunction>,
Manage<TFunction>,
};

// class member function pointer const
template <typename T, typename TRet, typename... TArgs,
TRet (T::*Method)(TArgs...) const>
struct MethodPtr<Method> {
static constexpr auto kMethod = Method;
T* instance;
};
template <typename TCallable, typename TSource, typename TRet,
typename... TArgs>
/**
* \brief Whether a callable can be stored and invoked by SmallFunction.
*
* For non-void results, the invocation result must be implicitly convertible
* to TRet; explicit-only conversions are rejected. Void results accept and
* discard any invocation result, including results requiring explicit
* conversion.
*/
concept CompatibleCallable =
std::constructible_from<TCallable, TSource> &&
std::is_nothrow_constructible_v<TCallable, TSource> &&
std::is_nothrow_move_constructible_v<TCallable> &&
std::invocable<TCallable&, TArgs&&...> &&
(std::is_void_v<TRet> ||
std::is_convertible_v<std::invoke_result_t<TCallable&, TArgs&&...>, TRet>);
} // namespace small_function_internal

static constexpr std::size_t kDefaultSize = sizeof(void*) * 4;
static constexpr std::size_t kDefaultAlignment = alignof(void*);
Expand All @@ -151,45 +136,65 @@ class SmallFunction<TRet(TArgs...), Size, Alignment> {
}
}

/**
* \brief Construction for any callable types except MethodPtr.
* By callable it means any type with operator() defined, like lambda or any
* user provided functor.
*/
template <typename TFunctor>
requires(requires {
{ std::invocable<TFunctor, TArgs...> };
{ std::same_as<std::invoke_result_t<TFunctor, TArgs...>, TRet> };
{ !std::same_as<std::decay_t<TFunctor>, SmallFunction> };
})
SmallFunction(TFunctor&& functor) noexcept
requires(!MethodPtrType<std::decay_t<TFunctor>> &&
!std::same_as<std::decay_t<TFunctor>, SmallFunction> &&
small_function_internal::CompatibleCallable<
std::decay_t<TFunctor>, TFunctor, TRet, TArgs...>)
SmallFunction(TFunctor&& functor) noexcept // NOLINT(*explicit*)
: vtable_{&small_function_internal::VTableForT<std::decay_t<TFunctor>,
TRet, TArgs...>} {
using Type = std::decay_t<TFunctor>;
static_assert(sizeof(Type) <= Size, "TFunctor must fit into storage");

static_assert(sizeof(Type) <= Size,
"SmallFunction target size exceeds storage size");
static_assert(alignof(Type) <= Alignment,
"SmallFunction target alignment exceeds storage alignment");
new (storage_.data()) Type{std::forward<TFunctor>(functor)};
}

SmallFunction(TRet (*func_ptr)(TArgs...)) noexcept
/**
* \brief Construction for MethodPtr types.
* This distincts from the any callable types constructor just for future
* development and its own static assert message.
*/
template <typename MPtr>
requires(MethodPtrType<std::decay_t<MPtr>> &&
small_function_internal::CompatibleCallable<std::decay_t<MPtr>,
MPtr, TRet, TArgs...>)
SmallFunction(MPtr&& method_ptr) noexcept // NOLINT(*explicit*)
: vtable_{&small_function_internal::VTableForT<std::decay_t<MPtr>, TRet,
TArgs...>} {
using Type = std::decay_t<MPtr>;
static_assert(sizeof(Type) <= Size,
"SmallFunction MethodPtr target size exceeds storage size");
static_assert(
alignof(Type) <= Alignment,
"SmallFunction MethodPtr target alignment exceeds storage alignment");
new (storage_.data()) Type{std::forward<MPtr>(method_ptr)};
}

/**
* \brief Construction for free function
* It accepts any function pointer type and stores it in the storage.
*/
SmallFunction(TRet (*func_ptr)(TArgs...)) noexcept // NOLINT(*explicit*)
: vtable_{
&small_function_internal::VTableForFreeFunction<TRet, TArgs...>} {
&small_function_internal::VTableForFreeFunction<TRet (*)(TArgs...),
TRet, TArgs...>} {
using FuncType = decltype(func_ptr);
static_assert(sizeof(FuncType) <= Size, "pointer must fit into storage");
static_assert(sizeof(FuncType) <= Size,
"SmallFunction function pointer size exceeds storage size");
static_assert(
alignof(FuncType) <= Alignment,
"SmallFunction function pointer alignment exceeds storage alignment");
new (storage_.data()) FuncType{func_ptr};
}

template <typename T, typename... UArgs, TRet (T::*Method)(UArgs...)>
SmallFunction(MethodPtr<Method> func_ptr) noexcept
: vtable_{&small_function_internal::VTableForClassMember<T, Method, TRet,
TArgs...>} {
static_assert(Size >= sizeof(T*), "pointer must fit into storage");
new (storage_.data()) T* {func_ptr.instance};
}

template <typename T, typename... UArgs, TRet (T::*Method)(UArgs...) const>
SmallFunction(MethodPtr<Method> func_ptr) noexcept
: vtable_{&small_function_internal::VTableForClassMember<T, Method, TRet,
TArgs...>} {
static_assert(Size >= sizeof(T*), "pointer must fit into storage");
new (storage_.data()) T* {func_ptr.instance};
}

SmallFunction(SmallFunction const& other) = delete;

SmallFunction(SmallFunction&& other) noexcept : vtable_{other.vtable_} {
Expand Down Expand Up @@ -222,8 +227,7 @@ class SmallFunction<TRet(TArgs...), Size, Alignment> {
*/
TRet operator()(TArgs... args) const {
assert(vtable_ != nullptr && "SmallFunction is not initialized");
return vtable_->invoke(const_cast<std::uint8_t*>(storage_.data()),
std::forward<TArgs>(args)...);
return vtable_->invoke(storage_.data(), std::forward<TArgs>(args)...);
}

explicit operator bool() const noexcept { return vtable_ != nullptr; }
Expand All @@ -238,7 +242,7 @@ class SmallFunction<TRet(TArgs...), Size, Alignment> {
}

VTable const* vtable_{};
Storage storage_{};
mutable Storage storage_{};
};

template <typename TFunction>
Expand Down
1 change: 1 addition & 0 deletions tests/test-types/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ cmake_minimum_required( VERSION 3.16 )
list(APPEND test_srcs
main.cpp
test-aligned-storage.cpp
test-method-ptr.cpp
test-small-function.cpp
test-result.cpp
)
Expand Down
2 changes: 2 additions & 0 deletions tests/test-types/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ void setUp() {}
void tearDown() {}

extern int test_aligned_storage();
extern int test_method_ptr();
extern int test_small_function();
extern int test_result();

int main() {
int res = 0;
res += test_aligned_storage();
res += test_method_ptr();
res += test_small_function();
res += test_result();
return res;
Expand Down
Loading
Loading