Simple C++ allocator for POD types Edit 1 after first review: I changed to use malloc / free since apparently it's also easier to optimize than new[] / delete[] : https://godbolt.org/g/CZy28o ; of course this adds an additional requirement on the alignment.

template &lt;class T&gt;
struct pod_allocator
{
    static_assert(std::is_pod_v&lt;T&gt;, "can only be used with POD types");
    static_assert(alignof(T) &lt;= alignof(std::max_align_t), "type must not have specific alignment requirements");
    using value_type = T;
    auto allocate(std::size_t num)  { return (T*) malloc(sizeof(T) * num); }
    void deallocate(T* p, std::size_t)   { free(p); }
};




The incentive here is to allow some compilers (clang as of clang-6.0 for instance) to remove allocations in some cases, by observing that they are able to optimize out new/delete pairs but not ::operator new/::operator delete pairs : see the following in https://godbolt.org/g/jumtCU

#include &lt;algorithm&gt;
#include &lt;numeric&gt;
#include &lt;cinttypes&gt;
int foo(int* src)
{
   constexpr int n = 10;    
   auto x = new int[n];
   for(int i = 0; i &lt; n; i++)
   {
       if(i%2 == 0)
         x[i] = src[n - i - 1] * 2;
       else
         x[i] = src[i] * src[i];
   }
   int z = 0;
   if(x[1] &lt; 1) z = 10; 
   else z = x[2] + x[4] - x[1] - x[3];

   delete[] x;

   return z;
}


While the vector version... well... produces "suboptimal" assembly: https://godbolt.org/g/cZvSRb .

#include &lt;algorithm&gt;
#include &lt;numeric&gt;
#include &lt;cinttypes&gt;
#include &lt;vector&gt;
int foo(int* src)
{
   constexpr int n = 10;    
   std::vector&lt;int&gt; x(n);
   for(int i = 0; i &lt; n; i++)
   {
       if(i%2 == 0)
         x[i] = src[n - i - 1] * 2;
       else
         x[i] = src[i] * src[i];
   }
   int z = 0;
   if(x[1] &lt; 1) z = 10; 
   else z = x[2] + x[4] - x[1] - x[3];

   return z;
}


Hence, here is the code of the allocator, which allows us to claim back much better codegen (https://godbolt.org/g/hvPY14) : 

template &lt;class T&gt;
class pod_allocator
{
  public:
    static_assert(std::is_pod_v&lt;T&gt;, "can only be used with POD types");
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&amp;;
    using const_reference = const T&amp;;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    using is_always_equal = std::true_type;

    constexpr T* address(T&amp; value) const noexcept { return &amp;value; }
    constexpr const T* address(const T&amp; value) const noexcept { return &amp;value; }

    constexpr auto max_size() const noexcept {
        return std::numeric_limits&lt;std::size_t&gt;::max() / sizeof(T);
    }

    auto allocate(std::size_t num) { return new T[num]; }
    void deallocate(T* p, std::size_t) { delete[] p; }

    constexpr void construct(T*, const T&amp;) noexcept { }
    constexpr void destroy(T*) noexcept { }
};

template&lt;class T1, class T2&gt;
constexpr bool operator==(const pod_allocator&lt;T1&gt;&amp;, const pod_allocator&lt;T2&gt;&amp;) noexcept { return true; }

template&lt;class T1, class T2&gt;
constexpr bool operator!=(const pod_allocator&lt;T1&gt;&amp;, const pod_allocator&lt;T2&gt;&amp;) noexcept { return false; }

template&lt;typename T&gt;
using pod_vector = std::vector&lt;T, pod_allocator&lt;T&gt;&gt;;


Are there some subtle bugs that I could be introducing this way ? (not counting of course the lack of value-initialization, which is, in this case, a feature and not a bug).
If you're going to roll your own allocator in C++17, you can save yourself a lot of grief by just leaving everything to default. The only things you need to include are value_type, allocate(), deallocate(), and operators == and !=.

In fact, I believe by not using the defaults, you've introduced a nasty little bug. Try doing this: auto v = pod_vector&lt;int&gt;(5, 42);. This should create a vector with five ints, all set to 42. But without construct() properly defined, who knows what will happen?

My advice:


Your allocator should be 4 lines.


the static_assert
value_type
allocate()
deallocate()



(And you need the == and != comparisons too.)

At the very least, either remove construct() or give it a sensible implementation (i.e., placement new). With destroy() it's less of an issue because you're only dealing with PODs.



AFTER EDIT 1: I'd wager the reason you're seeing better code generation for malloc() than new is that new throws std::bad_alloc automatically when it can't allocate.

I suspect if you changed your allocate() function to:

auto allocate(std::size_t num)  {
    auto const p = (T*) malloc(sizeof(T) * num);
    if (!p)
        throw std::bad_alloc{};
    return p;
}


you'd see a very different code gen situation.

As it stands, if you get an allocation failure, you're just returning a null pointer... which will lead to undefined behaviour, because allocate() must return a pointer to allocated memory.