1

Sorry for the long title, here is what I'm trying to achieve: I have a small C++ class with a bool template parameter which, when true, disables its setter methods using std::enable_if. Here is a simplified example:

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;
};

This code compiles an work as expected: Handle< true > doesn't have the set method, Handle< false > has it.

Now I'm trying to bind this to Python using SWIG. I'm using the following file to generate the binding:

%module core

%include "Handle.h"

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

%{
#include "Test.h"
%}

SWIG generates the module without complaining, it compiles fine, but the set method is never bound, even in the specialized NonConstHandle. e.g. the following Python test fails with 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")

When I searched on the subject, I found lots of things related to enable_if and SWIG which leads me to think that it's supported, but I can't figure out why set is not generated, although there's no error / warning emitted by SWIG...

Any help appreciated ! Regards

Citron
  • 1,019
  • 9
  • 24
  • You need to change the order of the `#include` and the `%include`. Even then, I doubt that `SWIG` is able to resolve this. There is many `c++11`, `c++14`... features which are only supported in the sense that the keyword does not give compilation errors. – Jens Munk Mar 26 '19 at 23:14
  • enable_if works all the way back to C++98, although it wasn't standardised outside of boost until 11. – Flexo Mar 27 '19 at 07:43
  • @JensMunk Is there any reason why the order is important ? As far as I could see, changing this doesn't change a thing except the line where the #include appears in the generated cxx file, and in both cases it didn't seem to make much of a difference. – Citron Mar 27 '19 at 08:06
  • @Flexo what do you mean ? – Citron Mar 27 '19 at 08:06
  • In your case, it may not make a difference. I have encountered some cases where symbols used in SWIG `.i` files were resolved as undefined unless I included the relevant C headers. Another thing that you need to be aware of is that SWIG does not recurse headers – Jens Munk Mar 27 '19 at 08:21
  • @JensMunk Ok makes sense. And yes, I (painfully) realized that swig didn't recurse headers on another part of our bindings :D – Citron Mar 27 '19 at 08:38

1 Answers1

2

The problem here is that every time you create a template in C++ you need at least one %template directive in your SWIG interface in order to make it have any effect on the generated wrapper.

When people vaguely hint that std::enable_if works they typically mean two things. Firstly that it parses ok and secondly that %template works for them. Both things are true here.

Since you've used SFINAE inside your template class with a template function you need one %template for each. Otherwise the set member is ignored totally, as you've seen. Side stepping the SFINAE/enable_if bit of your question an example of template functions inside template classes is a good place to start.

So we can change your .i file to look something like this:

%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>;

The problem is that (having fixed a few minor bugs in it) your test python now hits "this should not print", because we've generated a (totally legal) set() function even in the const case by explicitly spelling out the template parameters instead of getting them deduced.

So we've generated code to call:

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

Which uh works in this instance, because it can compile just not in the intuitive way.

I'm not aware of a way of making the deduction happen here (which is a shame, because they're defaulted so it should be possible right? - Maybe one for a patch into SWIG trunk, although doing both the defaulting and the SFINAE is going to be tricky)

Fortunately there is a simple workaround using %ignore to drop the version we don't want too:

%module test

%{
#include "test.h"
%}

%include "test.h"

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

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

Which does then generate the code you expected.


It's worth noting that often it's simpler to explicitly spell out the way you want complex templated code to work when generating wrappers - you typically need extra helpers or tweaks to the interface to get it to work in Python in the way you hope for. So you could also solve your example by doing something like this:

%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>;

Or a similar trick:

%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;
};

Although note that in this last case you'll need to use %apply as well if you want to use the templates as arguments in/out of functions.

Flexo
  • 87,323
  • 22
  • 191
  • 272
  • Thanks a lot for the detailed explanation. Specializing the `set` method and using `%ignore` indeed works fine for my example. Problem with this and the other tricks you suggested is that in our codebase, most of the classes I need to bind don't have only 1 or 2 methods, but more like 100s, and it's a part that evolves quite regularly. So having to update the .i each time we change our implementation will be quite error prone. It's really frustrating that SWIG can't deduce templated methods from the class's template :( – Citron Mar 27 '19 at 10:18
  • general purpose deduction can never work completely without `%template` because the type information isn't there (e.g. `template void foo(T);`. I'm pretty sure it should be possible to get cases like yours to work where all the template types are defaulted, but there are a lot of moving parts in some already fairly complex code. – Flexo Mar 27 '19 at 18:41