How to deal with templates?

Introduction

I would like to introduce the following piece of code. I am going to use it for most exlanations.

// file point.h
template< class T>
struct point_t{
    T x, y;
};

template <class T>
double distance( const point_t<T>& point ){
    return sqrt( point.x * point.x + point.y*point.y );
}

struct environment_t{
   ...
   template< class T>
   T get_value(const std::string& name);
   ...
};

Template instantiation

First of all you should understand, that you can not export template itself, but only its instantiations.

You can instantiate template class using operator sizeof:

sizeof( point_t<int> );

In order to instantiate a function you have to call it:

void instantiate(){
    double x = distance( point_t<t>() );

    environment_t env;
    std::string path = env.get_value< std::string >( "PATH" );
    int version = env.get_value< int >( "VERSION" );
}

You should put that code in some header file, parsed by CastXML.

“Dynamic” instantiation

If you have a template class, which should be instantiated with many types, you can create a small code generator, which will “instantiate the class”. It is pretty easy to blend together the generated code and the existing one:

from module_builder import module_builder_t, create_text_fc

def generate_instantiations_string( ... ):
   ...

code = generate_instantiations_string( ... )

mb = module_builder_t( [ create_text_fc( code ), <<<other file names>>> ], ... )
...

Function create_text_fc allows you to extract declarations from the string, which contains valid C++ code. It creates temporal header file and compiles it.

Functions templated on return type

environment_t env;
std::string path = env.get_value< std::string >( "PATH" );
int version = env.get_value< int >( "VERSION" );

CastXML provides information for both instantiations:

  • get_value<int>
  • get_value< std::string >

But, in this case there is a catch: the name of both functions is “get_value”. The only difference is “return type”.

In this situation, Py++ will generate code that contains errors. If your are lucky, it depends on the compiler you use, the generated code will not compile. Otherwise, you will discover the errors while testing the bindings.

Generated code:

bp::class_< environment_t >( "environment_t" )
    ...
    .def( "get_value"
          , (int ( ::environment_t::* )( ::std::string const & ) )( &::environment_t::get_value ) )
    .def( "get_value"
          , (::std::string ( ::environment_t::* )( ::std::string const & ) )( &::environment_t::get_value ) );

The correct code:

bp::class_< environment_t >( "environment_t" )
    .def( "get_value"
          , (int ( ::environment_t::* )( ::std::string const & ) )( &::environment_t::get_value< int > ) )
          //--------------------------------------------------------------------------^^^^^^^^^^^^^^^^
    .def( "get_value"
          , (::std::string ( ::environment_t::* )( ::std::string const & ) )( &::environment_t::get_value< std::string > ) );
          //------------------------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^

The perfect one:

bp::class_< environment_t >( "environment_t" )
    ...
    .def( "get_value", &::environment_t::get_value< int > )
    .def( "get_value", &::environment_t::get_value< std::string > );

Work-around

Py++ contains a work-around to the problem:

mb = module_builder_t( ..., optimize_queries=False, ... )
environment = mb.class_( "environment_t" )
for f in environment.member_functions( "get_value" ):
    #set the function alias
    f.alias = f.name + "_" + f.return_type.decl_string
    #correct function name
    f.name = f.demangled_name
#you still want the queries to run fast
mb.run_query_optimizer()

Before you read the rest of the solution, you should understand what is “name mangling” means. If you don’t, consider reading about it on Wikipedia .

The solution is pretty simple. CastXML reports mangled and demangled function names. The demangled function name contains “real” function name: get_value< used type >. You only have to instruct Py++ to use it.

Py++ does not use by default demangled function name for mainly one reason. Demangled function name is a string that contains a lot of information. Py++ implements a parser, which extracts the only relevant one. The parser implementation is a little bit complex and was not heavily tested. By “heavily” I mean that I tested it on a lot of crazy use cases and on a real project, but there is always some new use case out there. I am almost sure it will work for you. The problem, we deal with, is rare, so by default “demangled_name” feature is turned off.

By the way, almost the same problem exists for template classes. But, in the classes use case Py++ uses demangled name by default.

Help wanted

I understand that the provided solutions are not perfect and that something better and simpler should be done. Unfortunatelly the priority of this task is low.

Allen Bierbaum has few suggestion that could improve Py++. He created a wiki page, that discuss possible solutions. Your contribution is welcome too!