Search code examples
c++perlswig

How to convert multidimensional map from c++ into hash in perl


I am new to swig, and I am trying to convert c++ map into a hash in Perl.

There is a Perl extension that using c++ that needs to return a multidimensional array or map to the Perl. I have used the template in the swig interface file but it giving empty results.

swig interface file :

%include "exception.i"
%exception {

    try {
        $action
        } catch (const std::exception &e) {

        SWIG_exception_fail(SWIG_RuntimeError, e.what());
        }
    }

%module new_tr
%{
/* Put headers and other declarations here */
#include "new_tr.h"
%}
%include <std_map.i>

%template(StringStringMap) std::map<int,  std::map<std::string,std::string>>;

%include "new_tr.h"

cpp file

     std::map<int,  std::map<std::string,std::string>> Memberfunction::m;
    std::map<int,  std::map<std::string,std::string>> Memberfunction::getResult()
    {
        return m;
    }

perl file

use strict;
use new_tr;
use Data::Dumper;

my $new=new new_tr::some();
print Dumper($new->getResult());

Current Output :

$VAR1 = bless( {}, 'new_tr::StringStringMap' );

Expecting Output: (multidimensional hash)

$VAR1 = bless( 0 =>    { 
                                'Brown' => 'Manager', 
                                'Smith' => 'Salesman', 
                                'Albert' => 'Salesman',  
                            },  
            1 =>  { 
                                'Penfold' => 'Designer', 
                                'Evans' => 'Tea-person', 
                                'Jurgens' => 'Manager',  
                            }, 'new_tr::StringStringMap' );

Solution

  • Here is an example of how you can use %typemap for a nested std::map.

    new_tr.i:

    %module new_tr
    %typemap(out) std::map<int, std::map<std::string,std::string>> {
        HV *hash = (HV *) newHV();
        for (auto const& item : $1) {
            HV *subhash;
            char *keysv;
            auto map = item.second;
            subhash = (HV *) newHV();
            for (auto const &item2 : map) {
                SV *sv;
                auto key2 = item2.first.c_str();
                auto value = item2.second.c_str();
                sv = newSVpvn( value, strlen(value) );
                hv_store (subhash, key2, strlen(key2), sv, 0);
            }
            auto key = std::to_string(item.first).c_str();
            hv_store (hash, key, strlen(key), (SV*) newRV_noinc((SV*)subhash), 0);
        }  
        $result = newRV_noinc((SV*) hash);
        sv_2mortal($result);
        argvi++;
    } 
    %{
    #include "new_tr.h"
    %}
    %include "new_tr.h"
    

    new_tr.h:

    #include <map>
    #include <string>
    using ssmap = std::map<int,  std::map<std::string,std::string>>;
    ssmap getResult();
    

    new_tr.cxx:

    #include "new_tr.h"
    
    ssmap getResult()
    {
        ssmap map = {
            {1, {{"Brown","Manager"}, {"Smith", "Salesman"}, {"Albert", "Salesman"}}},
            {2, {{"Penfold", "Designer"}, {"Evans", "Tea-person"}, {"Jurgens", "Manager"}}}
        };
    
        return map;
    }
    

    Then compile the module with:

    perl_include_dir=$(perl -MConfig -e'print $Config{archlib}')"/CORE"
    swig -perl5 -c++ -I/usr/include new_tr.i 
    g++ -fPIC -c new_tr.cxx
    g++ -I${perl_include_dir} -c -fPIC -g -o new_tr_wrap.o new_tr_wrap.cxx
    g++ -shared -L. new_tr.o new_tr_wrap.o -o new_tr.so
    

    and test it with test.pl:

    use strict;
    use warnings;
    use Data::Dumper;
    use lib '.';
    use new_tr;
    
    my $map = new_tr::getResult();
    print Dumper( $map );
    

    Output:

    $VAR1 = {
              '1' => {
                       'Albert' => 'Salesman',
                       'Smith' => 'Salesman',
                       'Brown' => 'Manager'
                     },
              '2' => {
                       'Penfold' => 'Designer',
                       'Jurgens' => 'Manager',
                       'Evans' => 'Tea-person'
                     }
            };