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

Posted

技术标签:

【中文标题】SWIG C++/Python 绑定和支持带有 std::enable_if 的条件成员【英文标题】:SWIG C++/Python binding and support of conditional members with std::enable_if 【发布时间】:2019-03-26 18:06:02 【问题描述】:

抱歉,标题太长了,这是我想要实现的目标:我有一个带有 bool 模板参数的小型 C++ 类,当它为真时,它使用 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&lt; true &gt; 没有 set 方法,Handle&lt; false &gt; 有。

现在我正在尝试使用 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 发出...

任何帮助表示赞赏! 问候

【问题讨论】:

您需要更改#include%include的顺序。即便如此,我怀疑SWIG 是否能够解决这个问题。有许多 c++11c++14... 功能仅在关键字不会产生编译错误的意义上得到支持。 enable_if 一直可以追溯到 C++98,尽管它直到 11 才在 boost 之外标准化。 @JensMunk 有什么理由说明订单很重要吗?据我所见,除了 #include 出现在生成的 cxx 文件中的行之外,更改它不会改变任何事情,并且在这两种情况下似乎都没有太大区别。 @Flexo 你是什么意思? 在你的情况下,它可能没有什么不同。我遇到了一些情况,其中 SWIG .i 文件中使用的符号被解析为未定义,除非我包含相关的 C 头文件。您需要注意的另一件事是 SWIG 不会递归标头 【参考方案1】:

这里的问题是,每次在 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 现在点击“这不应该打印”,因为我们已经生成了一个(完全合法的)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

【讨论】:

非常感谢您的详细解释。专门化 set 方法并使用 %ignore 确实适用于我的示例。这个和你建议的其他技巧的问题是,在我们的代码库中,我需要绑定的大多数类不仅有 1 或 2 个方法,而且更像是 100 个方法,而且它是相当有规律地发展的一部分。因此,每次我们更改实现时都必须更新 .i 将很容易出错。 SWIG 无法从类的模板中推断出模板化的方法,这真是令人沮丧:( 如果没有%template,通用扣除永远无法完全发挥作用,因为类型信息不存在(例如template &lt;typename T&gt; void foo(T);。我很确定应该有可能让像你这样的案例在哪里工作所有的模板类型都是默认的,但是在一些已经相当复杂的代码中有很多移动的部分。

以上是关于SWIG C++/Python 绑定和支持带有 std::enable_if 的条件成员的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有 Swig 的 Python 中使用 float **?

使用 SWIG 绑定 Python/C++ 模板

SWIG Python 绑定到本地代码不适用于 OpenCV 2.1

为 C 库生成 Python SWIG 绑定时未定义的符号

c 的 SWIG python 绑定找不到标准头文件的 _EXFUN

SWIG 不支持 include_next GNU 指令