How to extend a templated c++ class in python with SWIG to allow the [] operator

后端 未结 3 798
栀梦
栀梦 2020-12-19 10:17

I have a templated c++ array class which uses the standard vector class:

#include 
#include 

using namespace std;

template

        
相关标签:
3条回答
  • 2020-12-19 10:20

    You can extend each type separately, like this:

    %extend doubleArray1D {
    

    Note that extension is virtual in that it just tells SWIG to generate code for extra functions that will be part of exported class but such function only has access to public interface of your c++ class.

    If you have a whole bunch of template instances, you could define and use a SWIG macro:

    %define ArrayExtend(name, T)
    %extend name<T> {
        T& __getitem__(int i) {
        return (*self)[i];
        }
     }
    %enddef
    
    ArrayExtend(Array1D, double)
    ArrayExtend(Array1D, int)
    
    0 讨论(0)
  • 2020-12-19 10:32

    You can extend whole templates, without having to pick a specific type. For example, modifying your code as follows:

    %module test
    
    %{
    #include <vector>
    %}
    
    %inline %{
    template<typename T>
    class Array1D{
    private:
        std::vector<T> data_;
        size_t xsize_;
    public:
        Array1D(): xsize_(0) {};
    
        // creates vector of size nx and sets each element to t
        Array1D(const size_t& nx, const T& t): xsize_(nx) {
            data_.resize(xsize_, t);
        }
    
        T& operator[](const size_t i) {return data_.at(i);}
    };
    %}
    
    %extend Array1D {
       T __getitem__(size_t i) {
        return (*$self)[i];
      }
    }
    
    %template(intArray1D) Array1D<int>;
    %template(doubleArray1D) Array1D<double>;
    

    Which works as you'd hope because SWIG itself expands and fills in the types for T when it is generating the wrapper:

    In [1]: import test
    
    In [2]: a=test.intArray1D(10,1)
    
    In [3]: a[0]
    
    Out[3]: 1
    
    In [4]: a[10]
    
    terminate called after throwing an instance of 'std::out_of_range'
      what():  vector::_M_range_check
    zsh: abort      ipython
    

    Note: I swapped to size_t from int because they're not synonyms always and .at() instead of [] because the former will throw for an invalid index rather than invoke undefined behaviour. You can actually use SWIG's default exception library to do "smart" things with the exception for free:

    %module test
    
    %{
    #include <vector>
    %}
    
    %include <std_except.i>
    
    %inline %{
    template<typename T>
    class Array1D{
    private:
        std::vector<T> data_;
        size_t xsize_;
    public:
        Array1D(): xsize_(0) {};
    
        // creates vector of size nx and sets each element to t
        Array1D(const size_t& nx, const T& t): xsize_(nx) {
            data_.resize(xsize_, t);
        }
    
        T& operator[](const size_t i) {return data_.at(i);}
    };
    %}
    
    %extend Array1D {
       T __getitem__(size_t i) throw(std::out_of_range) {
        return (*$self)[i];
      }
    }
    
    %template(intArray1D) Array1D<int>;
    %template(doubleArray1D) Array1D<double>;
    

    Is sufficient (two lines of changes) to get a Python IndexError instead of a C++ exception, crash or other UB.

    0 讨论(0)
  • 2020-12-19 10:42

    To provide a more generic solution:

    %define ArrayExtendVal(name, T) %extend name { T getitem(int i) { return (*self)[i]; } } %enddef %define ArrayExtendRef(name, T) %extend name { T& getitem(int i) { return (*self)[i]; } } %enddef %define ArrayExtendConstRef(name, T) %extend name { const T& getitem(int i) { return (*self)[i]; } } %enddef ... %ignore myNamespace::myClass::operator[] %include "my_class.h" ArrayExtendVal(myClass, double); ArrayExtendRef(myClass, double); ArrayExtendConstRef(myClass, double);

    Note the %ignore directive that is missing in the other answers.

    0 讨论(0)
提交回复
热议问题