lundi 5 octobre 2015

How to detect whether type has overloaded version of operator new and delete?

Implementing custom allocator I found out, that for classes which overloads void * operator new(std::size_t) and void operator delete(void *) allocator std::allocator not uses overloaded versions of mentioned operators, but it uses global defined versions of them instead. Documentation of std::allocator said:

Allocates n * sizeof(T) bytes of uninitialized storage by calling ::operator new(std::size_t), but it is unspecified when and how this function is called.

I think it is possible to make an overload using SFINAE by std::is_class< value_type >{} || std::is_union< value_type >{} or something similar, to dispatch on whether std::allocator_traits< A >::value_type can (ever) provide overloaded versions of the operators and eventually using proper version of them. But it is not enough to use the predicate in such a form.

#include <type_traits>
#include <memory>
#include <new>
#include <vector>
#include <iostream>

#include <cstdlib>
#include <cstddef>

#define PP { std::cout << __PRETTY_FUNCTION__ << std::endl; }

#define ENABLE  std::enable_if_t<  (std::is_class< type >{} || std::is_union< type >{}) > * = nullptr
#define DISABLE std::enable_if_t< !(std::is_class< type >{} || std::is_union< type >{}) > * = nullptr

template< typename type >
void *
operator new(std::size_t n, ENABLE) noexcept(false)
{
    return type::operator new(n);
}

template< typename type >
void *
operator new(std::size_t n, DISABLE) noexcept(false)
{
    PP;
    return ::operator new(n);
}

template< typename type >
void
operator delete(void * p, ENABLE) noexcept
{
    return type::operator delete(p);
}

template< typename type >
void
operator delete(void * p, DISABLE) noexcept
{
    PP;
    return ::operator delete(p);
}

template< typename type >
struct allocator
{

    using value_type = type;

    value_type *
    allocate(std::size_t n) noexcept(false)
    {
        return static_cast< value_type * >(::operator new< value_type >(n));
    }

    void
    deallocate(value_type * p, std::size_t /*n*/) noexcept
    {
        return ::operator delete< value_type >(p);
    }

    template< typename rhs >
    constexpr
    bool
    operator == (allocator< rhs > const & /*_rhs*/) noexcept
    {
        return true;
    }

    template< typename rhs >
    constexpr
    bool
    operator != (allocator< rhs > const & _rhs) noexcept
    {
        return !operator == (_rhs);
    }

};

struct A
{

    void *
    operator new(std::size_t n) noexcept(false)
    {
        PP;
        return ::operator new(n);
    }

    void
    operator delete(void * p) noexcept
    {
        PP;
        return ::operator delete(p);
    }

};

struct B {};

int
main()
{
    {
        std::vector< A, allocator< A > > a;
        a.resize(1);
    }
    std::cout << std::endl;
    {
        std::vector< int, allocator< int > > i;
        i.resize(1);
    }
    std::cout << std::endl;
    {
        //std::vector< B, allocator< B > > b;
        //b.resize(1);
    }
    return EXIT_SUCCESS;
}

The output for A type and int type is:

static void *A::operator new(std::size_t)
static void A::operator delete(void *)

void *operator new(std::size_t, std::enable_if_t<!(std::is_class<type>({}) || std::is_union<type>({}))> *) [type = int]
void operator delete(void *, std::enable_if_t<!(std::is_class<type>({}) || std::is_union<type>({}))> *) [type = int]

I.e. dispatching is right, but for B class an error occured:

main.cpp:34:22: error: no member named 'operator delete' in 'B'
        return type::operator delete(p);
               ~~~~~~^
main.cpp:60:22: note: in instantiation of function template specialization 'operator delete<B>' requested here
            return ::operator delete< value_type >(p);
                     ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/alloc_traits.h:386:13: note: in instantiation of member function 'allocator<B>::deallocate' requested here
      { __a.deallocate(__p, __n); }
            ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/stl_vector.h:178:9: note: in instantiation of member function 'std::allocator_traits<allocator<B> >::deallocate' requested here
          _Tr::deallocate(_M_impl, __p, __n);
               ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/stl_vector.h:160:9: note: in instantiation of member function 'std::_Vector_base<B, allocator<B> >::_M_deallocate' requested here
      { _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage
        ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/stl_vector.h:253:7: note: in instantiation of member function 'std::_Vector_base<B, allocator<B> >::~_Vector_base' requested here
      vector()
      ^
main.cpp:116:46: note: in instantiation of member function 'std::vector<B, allocator<B> >::vector' requested here
            std::vector< B, allocator< B > > b;
                                             ^
main.cpp:19:22: error: no member named 'operator new' in 'B'
        return type::operator new(n);
               ~~~~~~^
main.cpp:54:50: note: in instantiation of function template specialization 'operator new<B>' requested here
            return static_cast< value_type * >(::operator new< value_type >(n));
                                                 ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/alloc_traits.h:360:20: note: in instantiation of member function 'allocator<B>::allocate' requested here
      { return __a.allocate(__n); }
                   ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/stl_vector.h:170:25: note: in instantiation of member function 'std::allocator_traits<allocator<B> >::allocate' requested here
        return __n != 0 ? _Tr::allocate(_M_impl, __n) : pointer();
                               ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/vector.tcc:557:34: note: in instantiation of member function 'std::_Vector_base<B, allocator<B> >::_M_allocate' requested here
              pointer __new_start(this->_M_allocate(__len));
                                        ^
/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/bits/stl_vector.h:676:4: note: in instantiation of member function 'std::vector<B, allocator<B> >::_M_default_append' requested here
          _M_default_append(__new_size - size());
          ^
main.cpp:117:15: note: in instantiation of member function 'std::vector<B, allocator<B> >::resize' requested here
            b.resize(1);
              ^
2 errors generated.

How to detect the presence of overloaded versions of class-scope operator new and operator delete?

Aucun commentaire:

Enregistrer un commentaire