Compare commits

...

2 Commits

Author SHA1 Message Date
木头云
5d56ef759f refactor(uninitialized): Improve construct() overload resolution
IMPROVEMENTS:
1. Add explicit zero-argument overload to avoid SFINAE ambiguity
2. Require at least one argument (A1) for parameterized overloads
3. Better separation between direct initialization and aggregate initialization

BENEFITS:
- Clearer intent: zero-argument construction is explicitly handled
- Avoids potential SFINAE ambiguity when empty parameter pack is used
- More maintainable: easier to understand which overload is selected
- Consistent with modern C++ best practices for variadic templates

TECHNICAL DETAILS:
- Zero-arg overload: Always uses T() for value initialization
- One-or-more-arg overload: Uses SFINAE to choose between:
  * T(args...) for types with matching constructor
  * T{args...} for aggregate types or types with initializer_list ctor

This is a code quality improvement and does not fix any compilation issues,
but provides better template overload resolution.
2025-12-01 09:58:53 +00:00
木头云
eede00165e fix(container_allocator): Fix MSVC compilation by correcting allocator semantics
ROOT CAUSE:
The allocate() function was incorrectly constructing objects during memory
allocation, violating C++ allocator requirements. MSVC's std::_Tree_node has
a deleted default constructor, causing compilation failure.

CHANGES:
- container_allocator::allocate() now only allocates raw memory without
  constructing objects (removed mem::$new and ipc::construct calls)
- container_allocator::deallocate() now only frees memory without
  destroying objects (removed mem::$delete and ipc::destroy_n calls)

WHY THIS FIXES THE ISSUE:
C++ allocator semantics require strict separation:
  * allocate()   -> raw memory allocation only
  * construct()  -> object construction with proper arguments
  * destroy()    -> object destruction
  * deallocate() -> memory deallocation only

Standard containers (like std::map) call construct() with proper arguments
(key, value) to initialize nodes, not allocate(). Since std::_Tree_node in
MSVC has no default constructor (= delete), attempting to construct it
without arguments always fails.

Fixes MSVC 2017 compilation error:
  error C2280: 'std::_Tree_node<...>::_Tree_node(void)':
  attempting to reference a deleted function
2025-12-01 09:24:58 +00:00
2 changed files with 28 additions and 24 deletions

View File

@ -21,20 +21,32 @@ namespace ipc {
* \see https://en.cppreference.com/w/cpp/memory/construct_at
*/
template <typename T, typename... A>
auto construct(void *p, A &&...args)
-> std::enable_if_t<::std::is_constructible<T, A...>::value, T *> {
// Overload for zero arguments - use value initialization
template <typename T>
T* construct(void *p) {
#if defined(LIBIPC_CPP_20)
return std::construct_at(static_cast<T *>(p), std::forward<A>(args)...);
return std::construct_at(static_cast<T *>(p));
#else
return ::new (p) T(std::forward<A>(args)...);
return ::new (p) T();
#endif
}
template <typename T, typename... A>
auto construct(void *p, A &&...args)
-> std::enable_if_t<!::std::is_constructible<T, A...>::value, T *> {
return ::new (p) T{std::forward<A>(args)...};
// Overload for one or more arguments - prefer direct initialization
template <typename T, typename A1, typename... A>
auto construct(void *p, A1 &&arg1, A &&...args)
-> std::enable_if_t<::std::is_constructible<T, A1, A...>::value, T *> {
#if defined(LIBIPC_CPP_20)
return std::construct_at(static_cast<T *>(p), std::forward<A1>(arg1), std::forward<A>(args)...);
#else
return ::new (p) T(std::forward<A1>(arg1), std::forward<A>(args)...);
#endif
}
// Overload for non-constructible types - use aggregate initialization
template <typename T, typename A1, typename... A>
auto construct(void *p, A1 &&arg1, A &&...args)
-> std::enable_if_t<!::std::is_constructible<T, A1, A...>::value, T *> {
return ::new (p) T{std::forward<A1>(arg1), std::forward<A>(args)...};
}
/**

View File

@ -63,26 +63,18 @@ public:
pointer allocate(size_type count) noexcept {
if (count == 0) return nullptr;
if (count > this->max_size()) return nullptr;
if (count == 1) {
return mem::$new<value_type>();
} else {
void *p = mem::alloc(sizeof(value_type) * count);
if (p == nullptr) return nullptr;
for (std::size_t i = 0; i < count; ++i) {
std::ignore = ipc::construct<value_type>(static_cast<byte *>(p) + sizeof(value_type) * i);
}
return static_cast<pointer>(p);
}
// Allocate raw memory without constructing objects
// Construction should be done by construct() member function
void *p = mem::alloc(sizeof(value_type) * count);
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type count) noexcept {
if (count == 0) return;
if (count > this->max_size()) return;
if (count == 1) {
mem::$delete(p);
} else {
mem::free(ipc::destroy_n(p, count), sizeof(value_type) * count);
}
// Deallocate raw memory without destroying objects
// Destruction should be done by destroy() member function before deallocate
mem::free(p, sizeof(value_type) * count);
}
template <typename... P>