Ответ 1
Итак, похоже, что мой код был совершенно легальным, это скорее проблема с clang++. В качестве альтернативы я могу злоупотреблять некоторыми не вполне определенными поведениями на C++. Но пока это маловероятно. Если бы любой юрист на языке С++ мог меня исправить, я был бы очень признателен этому.
Во всяком случае, я закончил использовать свое обходное решение, которое я улучшил, посмотрев на некоторый пример кода, представленный в комментариях к вопросу.
Если кому-то интересно, как выглядит настоящий код с помощью описанного трюка, я вставляю его здесь.
// Round down to a power of two multiple.
constexpr std::size_t Align(std::size_t n, std::size_t a) {
return n & ~(a - 1);
}
// Round up to a power of two multiple.
constexpr std::size_t AlignUp(std::size_t n, std::size_t a) {
return Align(n + a - 1, a);
}
namespace memory {
namespace detail {
// Calculate a data item alignment according to its size.
constexpr std::size_t Align(std::size_t size, std::size_t offset) {
return size < 0x08 ? ::AlignUp(offset, 0x04)
: size < 0x10 ? ::AlignUp(offset, 0x08)
: ::AlignUp(offset, 0x10);
}
// Services for placement of a given type instance within a memory chunk
// at the specified offset.
template <typename T, std::size_t S> class EntryLayout {
public:
using Type = T;
using Pointer = T *;
static constexpr std::size_t Size = sizeof(Type);
static constexpr std::size_t Offset = Align(Size, S);
static constexpr std::size_t EndOffset = Offset + Size;
static Pointer Instance(char *ptr) {
return reinterpret_cast<Pointer>(RawData(ptr));
}
template <typename... Args>
static Pointer Construct(char *ptr, Args &&... args) {
return new (RawData(ptr)) Type(std::forward<Args>(args)...);
}
static void Destruct(char *ptr) { Instance(ptr)->~Type(); }
private:
static char *RawData(char *ptr) { return ptr + Offset; }
};
// Services for placement of a number of types within a memory
// chunk at the specified offset.
template <std::size_t S, typename... Tail> class ChunkLayout {
public:
static constexpr std::size_t StartOffset = S;
static constexpr std::size_t EndOffset = S;
template <typename... Args> static void Construct(char *, Args...) {}
static void Destruct(char *) {}
};
// Recursive template specialization of the above.
template <std::size_t S, typename Head, typename... Tail>
class ChunkLayout<S, Head, Tail...>
: public ChunkLayout<EntryLayout<Head, S>::EndOffset, Tail...> {
public:
using EntryType = Head;
using HeadLayout = EntryLayout<Head, S>;
using TailLayout = ChunkLayout<HeadLayout::EndOffset, Tail...>;
static constexpr std::size_t StartOffset = S;
static constexpr std::size_t EndOffset = TailLayout::EndOffset;
static typename HeadLayout::Pointer Instance(char *ptr) {
return HeadLayout::Instance(ptr);
}
template <typename... Args> void Construct(char *ptr, Args... args) {
HeadLayout::Construct(ptr, args...);
TailLayout::Construct(ptr, args...);
}
void Destruct(char *ptr) {
TailLayout::Destruct(ptr);
HeadLayout::Destruct(ptr);
}
};
} // namespace detail
// Control of memory chunk free and used space.
class ChunkSpace {
public:
ChunkSpace(std::size_t size) noexcept : free_{size}, used_(0) {}
std::size_t Used() const { return used_; }
std::size_t Free() const { return free_; }
std::size_t Size() const { return free_ + used_; }
bool Alloc(std::size_t size) {
if (size > free_)
return false;
free_ -= size;
used_ += size;
return true;
}
void Reset(std::size_t size = 0) {
assert(size <= used_);
free_ = free_ + used_ - size;
used_ = size;
}
private:
std::size_t free_;
std::size_t used_;
};
template <typename... EntryType>
class Chunk : public detail::ChunkLayout<0, ChunkSpace, EntryType...> {
using Layout = detail::ChunkLayout<0, ChunkSpace, EntryType...>;
public:
Chunk(char *data, std::size_t size) : data_{data} {
assert(size > Layout::EndOffset);
// Construct ChunkSpace instance to bootstrap allocation.
Layout::HeadLayout::Construct(data_, size);
// Allocate space required for all the chunk data.
Alloc(Layout::EndOffset);
// Construct the rest of the chunk data.
Layout::TailLayout::Construct(data_);
}
~Chunk() {
Layout::Destruct(data_);
}
template <typename T>
T* Get() {
return decltype(Upcast<T>(this))::Instance(data_);
}
template <typename T>
const T* Get() const {
return decltype(Upcast<T>(this))::Instance(data_);
}
std::size_t Used() const { return Get<ChunkSpace>()->Used(); }
std::size_t Free() const { return Get<ChunkSpace>()->Free(); }
std::size_t Size() const { return Get<ChunkSpace>()->Size(); }
void *Allocate(std::size_t size) {
std::size_t offset = Used();
std::size_t aligned_offset = detail::Align(size, offset);
std::size_t offset_padding = aligned_offset - offset;
if (!Alloc(size + offset_padding))
return nullptr;
return data_ + aligned_offset;
}
private:
bool Alloc(std::size_t size) {
return Get<ChunkSpace>()->Alloc(size);
}
// Some C++ magic to upcast to the base class that contains layout info
// for a given entry type.
template <typename Head, std::size_t S, typename... Tail>
static typename detail::ChunkLayout<S, Head, Tail...>::HeadLayout
Upcast(const detail::ChunkLayout<S, Head, Tail...> *);
char *data_;
};
} // namespace memory
И теперь образец использования всего этого оборудования:
#include "chunk.h"
#include "iostream"
struct A {
int value = 0xa;
};
struct B {
int value = 0xb;
};
void alloc(memory::Chunk<A, B> &chunk, std::size_t size)
{
chunk.Allocate(size);
std::cout << "Allocate " << size << " bytes:" << std::endl;
std::cout << " used: " << chunk.Used() << std::endl;
std::cout << " free: " << chunk.Free() << std::endl;
}
int main()
{
char buffer[1024];
memory::Chunk<A, B> chunk(buffer, sizeof buffer);
std::cout << "used: " << chunk.Used() << std::endl;
std::cout << "free: " << chunk.Free() << std::endl;
A *a = chunk.Get<A>();
B *b = chunk.Get<B>();
std::cout << std::hex;
std::cout << "a: " << a->value << " b: " << b->value << std::endl;
std::cout << std::dec;
alloc(chunk, 1);
alloc(chunk, 2);
alloc(chunk, 4);
alloc(chunk, 8);
alloc(chunk, 16);
return 0;
}