Вопрос или проблема
Как получить указатель на перегруженную шаблонную член-функцию, получая указатель на непроверенную.
У меня следующая ситуация:
class C
{
public:
template <typename T>
std::shared_ptr<T> foo();
std::shared_ptr<SomeClass> foo(std::string_view sw);
};
class DerivedFromC : public C
{
// нет логики, касающейся foo
};
// в моих привязках
pybind11::class_<DerivedFromC, ...некоторые параметры...>(m, "Имя")
.def("foo", pybind11::overload_cast<std::string_view>(&DerivedFromC::foo));
Этот код не компилируется с сообщением об ошибке:
[build] pythonbind.hpp(51,21): ошибка: нет подходящей функции для вызова объекта типа 'const detail::overload_cast_impl<basic_string_view<char, char_traits<char>>>'
[build] 51 | .def("foo", pybind11::overload_cast<std::string_view>(&DerivedFromC::foo));
[build] | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[build] ...\build\vcpkg_installed\x64-windows\include\pybind11/detail/common.h(1106,20): примечание: кандидат-шаблон игнорируется: не удалось вывести аргумент шаблона 'Return'
[build] 1106 | constexpr auto operator()(Return (*pf)(Args...)) const noexcept -> decltype(pf) {
[build] | ^
[build] ...\build\vcpkg_installed\x64-windows\include\pybind11/detail/common.h(1111,20): примечание: кандидат-шаблон игнорируется: не удалось вывести аргумент шаблона 'Return'
[build] 1111 | constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept
[build] | ^
[build] ...\build\vcpkg_installed\x64-windows\include\pybind11/detail/common.h(1117,20): примечание: кандидат-шаблон функции не подходит: требуется 2 аргумента, но было предоставлено 1
[build] 1117 | constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept
[build] | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Я делаю что-то неправильно или есть особый способ получить указатель на эту функцию? Мне кажется, что функция overload_cast
должна была выбрать правильный перегруз, так как шаблонная версия не имеет аргументов.
Я решил свою проблему, вручную сделав static_cast
до правильной сигнатуры, как показано ниже.
pybind11::class_<DerivedFromC, ...некоторые параметры...>(m, "Имя")
.def("foo", static_cast<std::shared_ptr<SomeClass>(DerivedFromC::*)(std::string_view)>(&DerivedFromC::foo));
Тем не менее, это не кажется мне элегантным. Поэтому, если кто-то знает лучшее (более элегантное) решение, буду рад принять его как ответ.
Ответ или решение
В данной ситуации вы столкнулись с проблемой перегрузок в C++ при использовании библиотеки pybind11 для привязки функций класса к Python. Необходимо уточнить способ получения указателя на перегруженный метод, который не является шаблонным.
В вашем коде вы объявили два метода foo
в классе C
: один является шаблонным, а другой – обычным методом, принимающим параметр std::string_view
. В классе DerivedFromC
, который наследует от C
, нет переопределения этих методов, что означает, что вы хотите использовать базовую реализацию метода foo
.
Проблема возникает из-за того, что pybind11::overload_cast
не может однозначно выбрать нужный метод из-за одинаковых имён и различий в типах. Чтобы решить эту проблему, вы можете воспользоваться явным приведением типов.
Вы уже нашли решение с использованием static_cast
, как показано в вашем примере:
pybind11::class_<DerivedFromC, ...some options...>(m, "Name")
.def("foo", static_cast<std::shared_ptr<SomeClass> (DerivedFromC::*)(std::string_view)>(&DerivedFromC::foo));
Несмотря на то что это работающее решение, многие разработчики могут посчитать его "неэстетичным". Однако, к сожалению, в случае с перегруженными методами это довольно распространённая практика в C++. Вот несколько других подходов, которые могут быть применены для повышения читаемости кода:
-
Использование простого метода для привязки:
Создайте вспомогательный метод (например,bindFoo
) вDerivedFromC
, который будет вызывать нужный метод, и затем просто привяжите этот метод к Python.class DerivedFromC : public C { public: std::shared_ptr<SomeClass> bindFoo(std::string_view sw) { return foo(sw); } }; pybind11::class_<DerivedFromC, ...some options...>(m, "Name") .def("foo", &DerivedFromC::bindFoo);
-
Использование обертки с лямбдой:
Вы также можете использовать лямбду для создания обертки, если это допустимо в вашем контексте:pybind11::class_<DerivedFromC, ...some options...>(m, "Name") .def("foo", [](DerivedFromC& self, std::string_view sw) { return self.foo(sw); });
Эти альтернативные подходы позволяют избежать явных приведений типов, однако они не всегда могут соответствовать вашему стилю кодирования или требованиям к производительности. В конечном итоге использование static_cast
остается простым и эффективным способом, чтобы работать с перегруженными методами в контексте C++.