SWIG C++/Python 绑定和支持条件成员 std::enable_if

SWIG C++/Python binding and support of conditional members with std::enable_if

抱歉标题太长,这是我想要实现的目标:我有一个带有 bool 模板参数的小型 C++ class,当该参数为真时,它会禁用其 setter 方法,使用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 > 有。

现在我正在尝试使用 SWIG 将其绑定到 Python。我正在使用以下文件生成绑定:

%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 相关的东西,这让我认为它是受支持的,但我不明白为什么 set 不是已生成,尽管 SWIG 没有发出错误/警告...

感谢任何帮助! 此致

这里的问题是,每次在 C++ 中创建模板时,您的 SWIG 接口中至少需要一个 %template 指令,以使其对生成的包装器产生任何影响。

当人们含糊地暗示 std::enable_if 有效时,他们通常意味着两件事。首先,它解析正常,其次,%template 对它们有效。这两件事在这里都是真的。

由于您在模板中使用了 SFINAE class 和模板函数,因此每个模板都需要一个 %template。否则 set 成员将被完全忽略,如您所见。绕开你问题的 SFINAE/enable_if 部分 example of template functions inside template classes 是一个很好的起点。

所以我们可以将您的 .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>;

问题是(修复了其中的一些小错误)您的测试 python 现在命中 "this should 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;
};

尽管请注意,在最后一种情况下,如果您想使用模板作为函数的参数 in/out,您还需要使用 %apply