pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

Protected operator delete

Open adam-urbanczyk opened this issue 5 years ago • 3 comments

Issue description

I'm trying to wrap a complicated library (that I have no control over). For one reason or another some (base)classes have a protected operator delete. Trying to wrap a class like that results in a compilation error (cf. code below).

What is the canonical way of handling such a situation? NB: I don't want to instantiate the protected operator class I just want to register it as a part of the type system.

BTW: I found the following PR that might be related: #264

Reproducible example code

#include <pybind11/pybind11.h>

namespace py = pybind11;

class A{
  
  protected:
  
  	void operator delete(void* ptr){};
  
};

class B: public A{
  
  public:
  
  	using A::operator delete;
  
}; 

void dummy(A a){};


PYBIND11_MODULE(example, m) {
    
    py::class_<A>(m,"A");
    py::class_<B>(m,"B");
    
    py::implicitly_convertible<B, A>();
    
    m.def("dummy",&dummy);
    
};

For the record this is the error I get

/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h: In instantiation of ‘struct pybind11::detail::has_operator_delete<A, void>’:
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:1002:35:   required by substitution of ‘template<class T, typename std::enable_if<((! pybind11::detail::has_operator_delete<T>::value) && pybind11::detail::has_operator_delete_size<T>::value), int>::type <anonymous> > void pybind11::detail::call_operator_delete(T*, pybind11::size_t, pybind11::size_t) [with T = A; typename std::enable_if<((! pybind11::detail::has_operator_delete<T>::value) && pybind11::detail::has_operator_delete_size<T>::value), int>::type <anonymous> = <missing>]’
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:1356:41:   required from ‘static void pybind11::class_<type_, options>::dealloc(pybind11::detail::value_and_holder&) [with type_ = A; options = {}]’
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:1081:24:   required from ‘pybind11::class_<type_, options>::class_(pybind11::handle, const char*, const Extra& ...) [with Extra = {}; type_ = A; options = {}]’
example.cpp:27:24:   required from here
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:994:69: error: ‘static void A::operator delete(void*)’ is protected within this context
 template <typename T> struct has_operator_delete<T, void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
                                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.cpp:10:9: note: declared protected here
    void operator delete(void* ptr){};

adam-urbanczyk avatar Jan 13 '20 19:01 adam-urbanczyk

After a small modification the code compiles with clang (8) but still does not compile with gcc (I checked 8).

#include <pybind11/pybind11.h>

namespace py = pybind11;

class A{
  
  protected:
  
  	void operator delete(void* ptr){};
  
};

class B: public A{
  
  public:
  
  	using A::operator delete;
  
}; 

void dummy(A a){};


PYBIND11_MODULE(example, m) {
    
    py::class_<A,std::unique_ptr<A,py::nodelete>>(m,"A");
    py::class_<B>(m,"B");
    
    py::implicitly_convertible<B, A>();
    
    m.def("dummy",&dummy);
    
};

This time with the complete error:

In file included from example.cpp:1:
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h: In instantiation of ‘struct pybind11::detail::has_operator_delete<A, void>’:
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:1002:35:   required by substitution of ‘template<class T, typename std::enable_if<((! pybind11::detail::has_operator_delete<T>::value) && pybind11::detail::has_operator_delete_size<T>::value), int>::type <anonymous> > void pybind11::detail::call_operator_delete(T*, pybind11::size_t, pybind11::size_t) [with T = A; typename std::enable_if<((! pybind11::detail::has_operator_delete<T>::value) && pybind11::detail::has_operator_delete_size<T>::value), int>::type <anonymous> = <missing>]’
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:1356:41:   required from ‘static void pybind11::class_<type_, options>::dealloc(pybind11::detail::value_and_holder&) [with type_ = A; options = {std::unique_ptr<A, pybind11::nodelete>}]’
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:1081:24:   required from ‘pybind11::class_<type_, options>::class_(pybind11::handle, const char*, const Extra& ...) [with Extra = {}; type_ = A; options = {std::unique_ptr<A, pybind11::nodelete>}]’
example.cpp:27:56:   required from here
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:994:69: error: ‘static void A::operator delete(void*)’ is protected within this context
 template <typename T> struct has_operator_delete<T, void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
                                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.cpp:10:9: note: declared protected here
    void operator delete(void* ptr){};
         ^~~~~~~~
In file included from example.cpp:1:
/home/adam/anaconda2/envs/cpp-py-bindgen/include/python3.6m/pybind11/pybind11.h:994:69: error: ‘static void A::operator delete(void*)’ is protected within this context
 template <typename T> struct has_operator_delete<T, void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
                                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.cpp:10:9: note: declared protected here
    void operator delete(void* ptr){};

adam-urbanczyk avatar Jan 15 '20 18:01 adam-urbanczyk

It starts feeling weird to have a conversation with oneself but I hope that the community will find this useful.

The has_operator_delete (copy/paste from pybind11 source) seems to be the root cause. The below snippet does work with (checked on godbolt.org):

  • gcc 8.2 +
  • clang 6+
  • msvc 19 (cannot check lower versions)

but it fails on e.g.

  • gcc 8.1
  • clang 5

If this is expected, feel free to close the issue. Otherwise it might be good to indicate this in the docs (according to them much lower compiler versions are supported - ).

#include <type_traits>

using namespace std; 


template <typename T, typename SFINAE = void> struct has_operator_delete : std::false_type { };
template <typename T> struct has_operator_delete<T, void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
    : std::true_type { };
template <typename T, typename SFINAE = void> struct has_operator_delete_size : std::false_type { };
template <typename T> struct has_operator_delete_size<T, void_t<decltype(static_cast<void (*)(void *, size_t)>(T::operator delete))>>
    : std::true_type { };

class A{
  protected:
  	void operator delete(void* ptr){};
};

class B: public A{
  public:
  	using A::operator delete;  
}; 

int main()
{
	static_assert(!has_operator_delete<A>::value,"");  
}

adam-urbanczyk avatar Jan 21 '20 20:01 adam-urbanczyk

@adam-urbanczyk Does this issue have pull request? Did you find any workaround how to use class with protected delete() without this fix? Or is it possible to somehow inject this fix into pybind11?

Nic30 avatar Jun 27 '22 19:06 Nic30