Как получить указатель на перегруженную шаблонную член-функцию, получая указатель на непроверенную.

Вопрос или проблема

Как получить указатель на перегруженную шаблонную член-функцию, получая указатель на непроверенную.

У меня следующая ситуация:

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++. Вот несколько других подходов, которые могут быть применены для повышения читаемости кода:

  1. Использование простого метода для привязки:
    Создайте вспомогательный метод (например, 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);
  2. Использование обертки с лямбдой:
    Вы также можете использовать лямбду для создания обертки, если это допустимо в вашем контексте:

    pybind11::class_<DerivedFromC, ...some options...>(m, "Name")
       .def("foo", [](DerivedFromC& self, std::string_view sw) {
           return self.foo(sw);
       });

Эти альтернативные подходы позволяют избежать явных приведений типов, однако они не всегда могут соответствовать вашему стилю кодирования или требованиям к производительности. В конечном итоге использование static_cast остается простым и эффективным способом, чтобы работать с перегруженными методами в контексте C++.

Оцените материал
Добавить комментарий

Капча загружается...