#include <algorithm>
#include <cassert>
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <memory>
#include <vector>

using namespace std;

// All the JointPool / JointAllocator code would be 'library' code in practice and should be
// compared to the code required to implement joint allocation in the compiler. Once the library
// code is written it can be used fairly simply as demonstrated in the 'Usage' section below.

template <typename T>
struct JointPoolSizeTraits;

struct JointPool {
    explicit JointPool(size_t totalBytes)
        : m_jointBegin(new char[totalBytes]),
          m_jointCurrent(m_jointBegin.get()),
          m_jointEnd(m_jointBegin.get() + totalBytes) {}
    unique_ptr<char[]> m_jointBegin;
    char* m_jointCurrent = nullptr;
    char* m_jointEnd = nullptr;

    void* allocate(size_t bytes) {
        if (m_jointCurrent + bytes <= m_jointEnd) {
            auto p = m_jointCurrent;
            m_jointCurrent += bytes;
            return p;
        }
        assert(false);
        return nullptr;
    }
};

template <typename... Ts>
size_t GetJointPoolSize() {
    return 0;
}

template <typename T, typename... Ts, typename U, typename... Us>
size_t GetJointPoolSize(U&& arg, Us&&... args) {
    return JointPoolSizeTraits<T>::size(arg) + GetJointPoolSize<Ts...>(args...);
}

template <class T>
struct JointAllocator {
    typedef T value_type;

    JointAllocator(JointPool* pool) : m_pool(pool) {}
    JointAllocator(const JointAllocator&) = default;

    template <class U>
    JointAllocator(const JointAllocator<U>& other)
        : m_pool(other.m_pool) {}

    T* allocate(std::size_t n) { return reinterpret_cast<T*>(m_pool->allocate(n * sizeof(T))); }
    void deallocate(T* p, std::size_t n) {}

    JointPool* m_pool = nullptr;
};

template <typename T>
using JointVector = vector<T, JointAllocator<T>>;

template <typename T>
struct JointPoolSizeTraits<JointVector<T>> {
    static constexpr size_t size(size_t n) { return sizeof(T*) + sizeof(T) * n; }
};

// Usage - with library support for joint allocation, usage might look something like this:

struct Vector3 {
    float x, y, z;
};

struct Mesh {
private:
    JointPool pool;

public:
    template <typename ItPs, typename ItIs>
    Mesh(ItPs firstP, ItPs lastP, ItIs firstI, ItIs lastI)
        : pool(GetJointPoolSize<decltype(positions), decltype(indices)>(distance(firstP, lastP),
                                                                        distance(firstI, lastI))),
          positions(firstP, lastP, &pool),
          indices(firstI, lastI, &pool) {}

    Mesh(initializer_list<Vector3> ps, initializer_list<int> is)
        : Mesh(begin(ps), end(ps), begin(is), end(is)) {}

    JointVector<Vector3> positions;
    JointVector<int> indices;
};

ostream& operator<<(ostream& os, const Mesh& m) {
    for (const auto& pos : m.positions) os << "(" << pos.x << ", " << pos.y << ", " << pos.z << "), ";
    for (const auto& index : m.indices) os << index << ", ";
    return os << endl;
}

int main() {
    // initialize a Mesh with explicit verts and indices
    Mesh myMesh{{{0.f, 0.f, 0.f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, {0, 1, 2}};

    // initialize a Mesh with iterator pairs
    Mesh mySecondMesh{begin(myMesh.positions), end(myMesh.positions), begin(myMesh.indices),
                      end(myMesh.indices)};

    cout << myMesh << mySecondMesh;
}
