SWIG C ++ / Python绑定以及对带有std :: enable_if的条件成员的支持

时间:2019-03-26 18:06:02

标签: python c++ swig

抱歉,长标题是我要实现的目标:我有一个带有bool模板参数的小型C ++类,当设置为true时,它将使用std::enable_if禁用其setter方法。这是一个简化的示例:

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发出的警告...

任何帮助表示赞赏! 问候

1 个答案:

答案 0 :(得分:2)

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

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

由于您已经在模板类中使用了SFINAE和模板函数,因此每个模板都需要一个%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现在会命中“这不应该打印”,因为即使在const情况下,我们也已经生成了(完全合法的)set()函数通过显式地拼写模板参数而不是推导它们。

因此,我们生成了要调用的代码:

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

在这种情况下可以使用它,因为它不能以直观的方式进行编译。

我不知道在这种情况下进行扣除的方法(这很可惜,因为默认情况下,所以应该是对的吗?-也许是一个SWIG补丁程序之一主干,尽管同时进行默认设置和SFINAE都会很棘手)

幸运的是,有一个使用%ignore删除我们也不想要的版本的简单解决方法:

%module测试

%{
#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