SWIG C ++ / Python привязка и поддержка условных членов с std :: enable_if - PullRequest
1 голос
/ 26 марта 2019

Извините за длинный заголовок, вот что я пытаюсь достичь: у меня есть небольшой класс C ++ с параметром шаблона bool, который, когда true, отключает методы его установки с помощью std::enable_if.Вот упрощенный пример:

template< bool IS_CONST >
class Handle
{
public:

    Handle(void) : m_value(0) { }
    Handle(int value) : m_value(value) { }

    template < bool T = IS_CONST, typename COMPILED = typename std::enable_if< T == false >::type >
    void set(int value)
    {
        m_value = value;
    }

    int get(void) const
    {
        return m_value;
    }

private:
    int m_value;
};

Этот код компилирует работу, как и ожидалось: Handle< true > не имеет метода set, Handle< false > имеет его.

Теперь яЯ пытаюсь привязать это к Python, используя SWIG.Я использую следующий файл для генерации привязки:

%module core

%include "Handle.h"

%template(NonConstHandle) Handle< false >;
%template(ConstHandle) Handle< false >;

%{
#include "Test.h"
%}

SWIG генерирует модуль без жалоб, он прекрасно компилируется, но метод set никогда не привязывается, даже в специализированном NonConstHandle.например, следующий тест на Python завершается неудачно с AttributeError: 'NonConstHandle' object has no attribute 'set':

import core

handle = core.NonConstHandle()
assert(handle.get() == 0)
handle.set(1)
assert(handle.get() == 1)

const_handle = core.ConstHandle()
assert(const_handle .get() == 0)
try:
    const_handle .set(1)
    print("this should not print")
except:
    pass

print("all good")

Когда я искал предмет, я обнаружил много вещей, связанных с enable_if и SWIG, что наводит меня на мысль, что оно поддерживается, но я могу 't выяснить, почему set не генерируется, хотя SWIG не выдает никаких ошибок / предупреждений ...

Любая помощь приветствуется!Привет

1 Ответ

2 голосов
/ 27 марта 2019

Проблема здесь в том, что каждый раз, когда вы создаете шаблон в C ++, вам нужна как минимум одна директива %template в вашем интерфейсе SWIG, чтобы она могла влиять на сгенерированную оболочку.

Когда люди смутно намекают на то, что std::enable_if работает, они обычно означают две вещи. Во-первых, это нормально, а во-вторых, что %template работает для них. Обе вещи верны здесь.

Поскольку вы использовали SFINAE внутри класса вашего шаблона с функцией шаблона, вам нужно одно %template для каждого. В противном случае, как вы видели, элемент set полностью игнорируется. Боковой шаг по биту SFINAE / enable_if по вашему вопросу пример шаблонных функций внутри шаблонных классов - хорошее место для начала.

Так что мы можем изменить ваш .i файл, чтобы он выглядел примерно так:

%module test 

%{
#include "test.h"
%}

%include "test.h"

// Be explicit about what 'versions' of set to instantiate in our wrapper    
%template(set) Handle::set<false, void>;

%template(NonConstHandle) Handle<false>;
%template(ConstHandle) Handle<true>;

Проблема в том, что (исправив несколько незначительных ошибок в нем) ваш тестовый питон теперь нажимает «this not print», потому что мы сгенерировали (полностью допустимую) функцию set() даже в случае const явно расшифровка параметров шаблона вместо их вывода.

Итак, мы сгенерировали код для вызова:

Handle<true>::set<false, void>(int);

Что работает в этом случае, потому что он может компилироваться, но не интуитивно понятным способом.

Я не знаю, как здесь можно сделать вывод (это позор, потому что они по умолчанию, так что должно быть возможно, верно? - Может быть, один для патча в ствол SWIG хотя сделать дефолт и SFINAE будет сложно)

К счастью, существует простой обходной путь, использующий %ignore для удаления версии, которая нам тоже не нужна:

% тест модуля

%{
#include "test.h"
%}

%include "test.h"

%template(set) Handle::set<false, void>;
%ignore Handle<true>::set;

%template(NonConstHandle) Handle<false>;
%template(ConstHandle) Handle<true>;

Который затем генерирует ожидаемый код.


Стоит отметить, что зачастую проще явно указать, как нужно, чтобы сложный шаблонный код работал при генерации оболочек - вам обычно нужны дополнительные помощники или настройки интерфейса, чтобы заставить его работать в Python так, как вы надеетесь на , Таким образом, вы также можете решить свой пример, выполнив что-то вроде этого:

%module test

%{
#include "test.h"
%}

template <bool>
class Handle {
public:
    Handle(void);
    Handle(int value);

    int get(void) const;
};

template<>
class Handle<false>
{
public:
    Handle(void);
    Handle(int value);

    void set(int value);
    int get(void) const;
};

%template(NonConstHandle) Handle<false>;
%template(ConstHandle) Handle<true>;

Или подобный трюк:

%module test 

%{
#include "test.h"
typedef Handle<true> ConstHandle;
typedef Handle<false> NonConstHandle;
%}

struct ConstHandle {
    ConstHandle(void);
    ConstHandle(int value);

    int get(void) const; 
};

struct NonConstHandle
{
    NonConstHandle(void);
    NonConstHandle(int value);

    void set(int value);
    int get(void) const;
};

Хотя обратите внимание, что в этом последнем случае вам также нужно будет использовать %apply, если вы хотите использовать шаблоны в качестве аргументов в / из функций.

...